aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rwxr-xr-xINSTALL.txt31
-rw-r--r--LICENSE.txt699
-rw-r--r--app/controllers/admin_controller.rb16
-rw-r--r--app/controllers/admin_public_body_controller.rb28
-rw-r--r--app/controllers/admin_request_controller.rb4
-rw-r--r--app/controllers/application_controller.rb52
-rw-r--r--app/controllers/comment_controller.rb1
-rw-r--r--app/controllers/general_controller.rb2
-rw-r--r--app/controllers/public_body_controller.rb48
-rw-r--r--app/controllers/request_controller.rb87
-rw-r--r--app/controllers/user_controller.rb99
-rw-r--r--app/helpers/application_helper.rb6
-rw-r--r--app/helpers/link_to_helper.rb4
-rw-r--r--app/models/change_email_validator.rb42
-rw-r--r--app/models/comment.rb2
-rw-r--r--app/models/incoming_message.rb177
-rw-r--r--app/models/info_request.rb61
-rw-r--r--app/models/info_request_event.rb2
-rw-r--r--app/models/outgoing_mailer.rb2
-rw-r--r--app/models/outgoing_message.rb25
-rw-r--r--app/models/post_redirect.rb8
-rw-r--r--app/models/request_mailer.rb42
-rw-r--r--app/models/track_mailer.rb30
-rw-r--r--app/models/track_thing.rb2
-rw-r--r--app/models/track_things_sent_email.rb6
-rw-r--r--app/models/user.rb10
-rw-r--r--app/models/user_info_request_sent_alert.rb1
-rw-r--r--app/models/user_mailer.rb21
-rw-r--r--app/views/admin_public_body/import_csv.rhtml31
-rw-r--r--app/views/admin_request/show.rhtml10
-rw-r--r--app/views/admin_track/_some_tracks.rhtml2
-rw-r--r--app/views/admin_track/list.rhtml2
-rw-r--r--app/views/comment/new.rhtml2
-rw-r--r--app/views/general/frontpage.rhtml2
-rw-r--r--app/views/general/search.rhtml2
-rw-r--r--app/views/help/about.rhtml259
-rw-r--r--app/views/help/unhappy.rhtml4
-rw-r--r--app/views/layouts/default.rhtml24
-rw-r--r--app/views/public_body/list.rhtml53
-rw-r--r--app/views/public_body/show.rhtml18
-rw-r--r--app/views/request/_after_actions.rhtml2
-rw-r--r--app/views/request/_correspondence.rhtml2
-rw-r--r--app/views/request/_describe_state.rhtml2
-rw-r--r--app/views/request/_followup.rhtml55
-rw-r--r--app/views/request/_hidden_correspondence.rhtml37
-rw-r--r--app/views/request/_other_describe_state.rhtml2
-rw-r--r--app/views/request/_sidebar.rhtml3
-rw-r--r--app/views/request/details.rhtml54
-rw-r--r--app/views/request/new.rhtml4
-rw-r--r--app/views/request/show.rhtml46
-rw-r--r--app/views/request/show_response.rhtml2
-rw-r--r--app/views/request_mailer/overdue_alert.rhtml6
-rw-r--r--app/views/request_mailer/very_overdue_alert.rhtml14
-rw-r--r--app/views/user/_signin.rhtml2
-rw-r--r--app/views/user/show.rhtml3
-rw-r--r--app/views/user/signchange_send_confirm.rhtml30
-rw-r--r--app/views/user/signchangeemail.rhtml41
-rw-r--r--app/views/user/signchangeemail_confirm.rhtml14
-rw-r--r--app/views/user/signchangepassword.rhtml (renamed from app/views/user/signchange.rhtml)10
-rw-r--r--app/views/user/signchangepassword_confirm.rhtml (renamed from app/views/user/signchange_confirm.rhtml)0
-rw-r--r--app/views/user/signchangepassword_send_confirm.rhtml30
-rw-r--r--app/views/user_mailer/changeemail_already_used.rhtml9
-rw-r--r--app/views/user_mailer/changeemail_confirm.rhtml12
m---------commonlib0
-rw-r--r--config/crontab.ugly2
-rw-r--r--config/environment.rb17
-rw-r--r--config/general-example5
-rw-r--r--config/packages19
-rw-r--r--config/routes.rb6
-rw-r--r--db/migrate/082_change_raw_email_to_binary.rb4
-rw-r--r--db/migrate/083_add_indices_track_sent.rb9
-rw-r--r--db/schema.rb3
-rw-r--r--lib/sendmail_return_path.rb2
-rw-r--r--lib/tnef.rb40
-rw-r--r--public/down.default.html19
-rw-r--r--public/favicon.icobin0 -> 22382 bytes
-rw-r--r--public/images/README.txt2
l---------public/images/icon_application_vnd.openxmlformats-officedocument.presentationml.presentation_large.png1
l---------public/images/icon_application_vnd.openxmlformats-officedocument.spreadsheetml.sheet_large.png1
l---------public/images/icon_application_vnd.openxmlformats-officedocument.wordprocessingml.document_large.png1
-rw-r--r--public/stylesheets/main.css68
-rwxr-xr-xscript/clear-caches (renamed from script/clear-incoming-text-cache)0
-rwxr-xr-xscript/delete-old-things (renamed from script/delete-old-post-redirects)2
-rw-r--r--spec/controllers/request_controller_spec.rb184
-rw-r--r--spec/controllers/track_controller_spec.rb18
-rw-r--r--spec/controllers/user_controller_spec.rb180
-rw-r--r--spec/fixtures/fake-authority-type.csv3
-rw-r--r--spec/fixtures/incoming-request-attach-attachments.email54
-rw-r--r--spec/fixtures/incoming-request-oft-attachments.email385
-rw-r--r--spec/fixtures/incoming-request-tnef-attachments.email3024
-rw-r--r--spec/fixtures/psni.pdfbin0 -> 48057 bytes
-rw-r--r--spec/fixtures/raw_emails.yml2
-rw-r--r--spec/models/incoming_message_spec.rb68
-rw-r--r--spec/models/info_request_spec.rb83
-rw-r--r--spec/models/outgoing_mailer_spec.rb58
-rw-r--r--spec/models/outgoing_message_spec.rb2
-rw-r--r--spec/models/public_body_spec.rb32
-rw-r--r--spec/models/raw_email_spec.rb23
-rw-r--r--spec/models/request_mailer_spec.rb6
-rw-r--r--spec/models/track_mailer_spec.rb10
-rw-r--r--spec/models/user_spec.rb4
-rw-r--r--spec/models/xapian_spec.rb124
-rw-r--r--spec/spec_helper.rb10
-rw-r--r--spec/views/public_body/show.rhtml_spec.rb11
-rw-r--r--spec/views/request/show.rhtml_spec.rb25
-rw-r--r--todo.txt320
-rw-r--r--vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb57
-rw-r--r--vendor/ruby-msg/ChangeLog82
-rw-r--r--vendor/ruby-msg/FIXES56
-rw-r--r--vendor/ruby-msg/README128
-rw-r--r--vendor/ruby-msg/Rakefile77
-rw-r--r--vendor/ruby-msg/TODO184
-rwxr-xr-xvendor/ruby-msg/bin/mapitool195
-rw-r--r--vendor/ruby-msg/contrib/rtf2html.c155
-rw-r--r--vendor/ruby-msg/contrib/rtfdecompr.c105
-rw-r--r--vendor/ruby-msg/contrib/wmf.rb107
-rw-r--r--vendor/ruby-msg/data/mapitags.yaml4168
-rw-r--r--vendor/ruby-msg/data/named_map.yaml114
-rw-r--r--vendor/ruby-msg/data/types.yaml15
-rw-r--r--vendor/ruby-msg/lib/mapi.rb109
-rw-r--r--vendor/ruby-msg/lib/mapi/convert.rb61
-rw-r--r--vendor/ruby-msg/lib/mapi/convert/contact.rb142
-rw-r--r--vendor/ruby-msg/lib/mapi/convert/note-mime.rb274
-rw-r--r--vendor/ruby-msg/lib/mapi/convert/note-tmail.rb287
-rw-r--r--vendor/ruby-msg/lib/mapi/msg.rb440
-rw-r--r--vendor/ruby-msg/lib/mapi/property_set.rb269
-rw-r--r--vendor/ruby-msg/lib/mapi/pst.rb1806
-rw-r--r--vendor/ruby-msg/lib/mapi/rtf.rb169
-rw-r--r--vendor/ruby-msg/lib/mapi/types.rb51
-rw-r--r--vendor/ruby-msg/lib/mime.rb165
-rw-r--r--vendor/ruby-msg/lib/orderedhash.rb218
-rwxr-xr-xvendor/ruby-msg/lib/rtf.rb109
-rw-r--r--vendor/ruby-ole/ChangeLog62
-rw-r--r--vendor/ruby-ole/README115
-rw-r--r--vendor/ruby-ole/Rakefile209
-rwxr-xr-xvendor/ruby-ole/bin/oletool41
-rw-r--r--vendor/ruby-ole/data/propids.yaml56
-rw-r--r--vendor/ruby-ole/lib/ole/base.rb7
-rw-r--r--vendor/ruby-ole/lib/ole/file_system.rb2
-rw-r--r--vendor/ruby-ole/lib/ole/ranges_io.rb231
-rw-r--r--vendor/ruby-ole/lib/ole/storage.rb3
-rwxr-xr-xvendor/ruby-ole/lib/ole/storage/base.rb916
-rw-r--r--vendor/ruby-ole/lib/ole/storage/file_system.rb423
-rw-r--r--vendor/ruby-ole/lib/ole/storage/meta_data.rb148
-rw-r--r--vendor/ruby-ole/lib/ole/support.rb256
-rw-r--r--vendor/ruby-ole/lib/ole/types.rb2
-rw-r--r--vendor/ruby-ole/lib/ole/types/base.rb251
-rw-r--r--vendor/ruby-ole/lib/ole/types/property_set.rb165
149 files changed, 18827 insertions, 728 deletions
diff --git a/.gitignore b/.gitignore
index aa5036394..45cefbc4a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,6 @@
/coverage
/sqlitedbs
/cache
+/old-cache
.*.swp
*~
diff --git a/INSTALL.txt b/INSTALL.txt
index 7fcdb5568..6a4aadbb7 100755
--- a/INSTALL.txt
+++ b/INSTALL.txt
@@ -15,11 +15,16 @@ Commands are intended to be run via the terminal or over ssh.
1. Package Installation
-----------------------
-Firstly, in terminal, navigate to the foi folder where this install guide lives.
+Firstly, in a terminal, navigate to the whatdotheyknow folder where this
+install guide lives.
-Install the packages that are listed in config/packages using apt-get eg:
+Install the packages that are listed in config/packages using apt-get e.g.:
-> sudo apt-get install `cat config/packages`
+> sudo apt-get install `cut -d " " -f 1 config/packages`
+
+Some of the files also have a version number listed in config/packages - check
+that you have appropriate versions installed. Some also list "|" and offer
+a choice of packages.
Some of the required packages only exist in the mysociety debian archive (or we
have updated versions there), so you will want to add that to your
@@ -28,9 +33,6 @@ have updated versions there), so you will want to add that to your
deb http://debian.mysociety.org etch main non-free contrib
deb-src http://debian.mysociety.org etch main non-free contrib
-libgems-ruby1.8 has recently been replaced by rubygems1.8, so you may need to
-update the packages file to reflect this, depending on your version.
-
2. Configure Database
---------------------
@@ -44,7 +46,7 @@ If you don't have it installed:
Now we need to set up the database config file to contain the name,
username and password of your postgres database.
-* copy database.yml-example to database.yml in foi/config
+* copy database.yml-example to database.yml in whatdotheyknow/config
* edit it to point to your local postgresql database in the development
and test sections and create the databases:
@@ -70,15 +72,14 @@ constraints whilst running the tests they also need to be a superuser.
3. Deployment
-------------
-In the 'mysociety' directory above 'foi', run
-
-> ./bin/rails-post-deploy foi
+In the 'whatdotheyknow' directory, run:
+> ./script/rails-post-deploy
(This will need execute privs so chmod 755 if necessary)
This sets up directory structures, creates logs, etc.
-Next, if you have a foi/config/rails_env.rb file, delete it, so that
+Next, if you have a whatdotheyknow/config/rails_env.rb file, delete it, so that
tests run against our test database, rather than the development one.
(Otherwise, any data you create in development will be blown away every
time you run the tests.)
@@ -97,7 +98,7 @@ Make sure everything looks OK:
> rake spec
-If there are failures here, something has gone wrong with the preceeding
+If there are failures here, something has gone wrong with the preceding
steps. You might be able to move on to the next step, depending on how
serious they are, but ideally you should try to find out what's gone
wrong.
@@ -109,11 +110,11 @@ run the following to get the server running (may need to chmod 755 again)
> ./script/server --environment=development
or if you want the server to be available on the network and not just
-on localhost tell it your ip address by running
+on localhost tell it your IP address by running
> ./script/server --environment=development --binding=10.0.0.11
-Obviously change 10.0.0.11 to your own IP address
+Obviously, change 10.0.0.11 to your own IP address.
5. Success
----------
@@ -134,7 +135,7 @@ like to do something similar.
Alternatively, update the code so that
* By default, admin pages use normal site authentication (checking user admin
level 'super').
-* Create an option in config/general which lest mySociety override that
+* Create an option in config/general which lets mySociety override that
behaviour.
And send us the patch!
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 000000000..f359b01d3
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,699 @@
+ mySociety.org Software Licensing
+
+Most of the software in this directory is Copyright (c) 2004-2008 UK
+Citizens Online Democracy.
+
+Unless otherwise stated in particular files or directories, this
+software is free software; you can redistribute it and/or modify it
+under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Note in particular,
+- acts_as_xapian in vendor/plugins/ is licensed with an MIT license.
+
+Can you explain briefly what the GNU Affero GPL is? We offer the
+source code of our websites to our users. The GNU Affero GPL has the
+requirement that anyone else using that code for their own websites
+also does the courtesy of offering the source code to their users.
+
+Why not use the GPL? The GPL guarantees that anyone who gets a binary
+version of the software also gets the source code so they can modify
+it. Since users of websites never get the binary, just HTML pages, it
+is no better a license than a BSD style license would be for them.
+For this reason, we use the GNU Affero GPL.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Affero General Public License for more details.
+
+Information about the GNU Affero GPL:
+http://www.fsf.org/licensing/licenses/agpl-3.0.html
+
+A copy of the GNU Affero General Public License follows.
+
+-----------------------------------------------------------------------
+
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
+
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index 7f8cfbd67..ca5538e03 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -19,19 +19,9 @@ class AdminController < ApplicationController
# Expire cached attachment files for a request
def expire_for_request(info_request)
- # Clear out cached entries - use low level disk removal, even though we
- # are clearing results from caches_action, for several reasons:
- # * We can't use expire_action here, as it doesn't seem to be
- # compatible with the :only_path we used in the caches_action
- # call.
- # * Removing everything is simpler than having to get all the
- # parameters right for the path, and calling for HTML version vs. raw
- # attachment version.
- # * We cope properly with filenames changed by censor rules, which
- # change the URL.
- # * We could use expire_fragment with a Regexp, but it walks the whole
- # cache which is insanely slow
- cache_subpath = File.join(self.cache_store.cache_path, "views/request/#{info_request.id}")
+ # Clear out cached entries, by removing files from disk (the built in
+ # Rails fragment cache made doing this and other things too hard)
+ cache_subpath = foi_fragment_cache_all_for_request(info_request)
FileUtils.rm_rf(cache_subpath)
# Remove the database caches of body / attachment text (the attachment text
diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb
index 74a3a86c6..bce04ff98 100644
--- a/app/controllers/admin_public_body_controller.rb
+++ b/app/controllers/admin_public_body_controller.rb
@@ -120,7 +120,15 @@ class AdminPublicBodyController < AdminController
def import_csv
if params[:csv_file]
- if not params[:tag].empty?
+ if !params[:tag].empty?
+ if params['commit'] == 'Dry run'
+ dry_run_only = true
+ elsif params['commit'] == 'Upload'
+ dry_run_only = false
+ else
+ raise "internal error, unknown button label"
+ end
+
# Try with dry run first
csv_contents = params[:csv_file].read
en = PublicBody.import_csv(csv_contents, params[:tag], true, admin_http_auth_user())
@@ -128,14 +136,18 @@ class AdminPublicBodyController < AdminController
notes = en[1]
if errors.size == 0
- # And if OK, with real run
- en = PublicBody.import_csv(csv_contents, params[:tag], false, admin_http_auth_user())
- errors = en[0]
- notes = en[1]
- if errors.size != 0
- raise "dry run mismatched real run"
+ if dry_run_only
+ notes.push("Dry run was successful, real run would do as above.")
+ else
+ # And if OK, with real run
+ en = PublicBody.import_csv(csv_contents, params[:tag], false, admin_http_auth_user())
+ errors = en[0]
+ notes = en[1]
+ if errors.size != 0
+ raise "dry run mismatched real run"
+ end
+ notes.push("Import was successful.")
end
- notes.push("Import was successful.")
end
@errors = errors.join("\n")
@notes = notes.join("\n")
diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb
index ff2772b0e..f077691ff 100644
--- a/app/controllers/admin_request_controller.rb
+++ b/app/controllers/admin_request_controller.rb
@@ -215,10 +215,10 @@ class AdminRequestController < AdminController
# Bejeeps, look, sometimes a URL is something that belongs in a controller, jesus.
# XXX hammer this square peg into the round MVC hole - should be calling main_url(upload_response_url())
post_redirect = PostRedirect.new(
- :uri => upload_response_url(:url_title => info_request.url_title),
+ :uri => main_url(upload_response_url(:url_title => info_request.url_title, :only_path => true)),
:user_id => user.id)
post_redirect.save!
- url = confirm_url(:email_token => post_redirect.email_token)
+ url = main_url(confirm_url(:email_token => post_redirect.email_token, :only_path => true))
flash[:notice] = 'Send "' + name + '" &lt;<a href="mailto:' + email + '">' + email + '</a>&gt; this URL: <a href="' + url + '">' + url + "</a> - it will log them in and let them upload a response to this request."
redirect_to request_admin_url(info_request)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5055519ec..9ee1c250b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -40,21 +40,12 @@ class ApplicationController < ActionController::Base
before_filter :session_remember_me
def session_remember_me
# Reset the "sliding window" session expiry time.
- if session[:remember_me]
- expire_time = 1.month.from_now
- # "Why is session[:force_new_cookie] set to Time.now? In order for the “sliding window”
- # concept to work, a fresh cookie must be sent with every response. Rails only
- # sends a cookie when the session data has changed so using a value like Time.now
- # ensures that it changes every time. What I have actually found is that some
- # internal voodoo causes the session data to change slightly anyway but it’s best
- # to be sure!"
- session[:force_new_cookie] = Time.now
- else
- expire_time = nil
- end
- # if statement here is so test code runs
- if session.instance_variable_get(:@dbman)
- session.instance_variable_get(:@dbman).instance_variable_get(:@cookie_options)['expires'] = expire_time
+ if request.env['rack.session.options']
+ if session[:remember_me]
+ request.env['rack.session.options'][:expire_after] = 1.month
+ else
+ request.env['rack.session.options'][:expire_after] = nil
+ end
end
end
@@ -100,6 +91,27 @@ class ApplicationController < ActionController::Base
controller_example_group.get params[:action], params
end
+ # Used to work out where to cache fragments. We add an extra path to the
+ # URL using the first three digits of the info request id, because we can't
+ # have more than 32,000 entries in one directory on an ext3 filesystem.
+ def foi_fragment_cache_part_path(param)
+ path = url_for(param)
+ id = param['id'] || param[:id]
+ first_three_digits = id.to_s()[0..2]
+ path = path.sub("/request/", "/request/" + first_three_digits + "/")
+ return path
+ end
+ def foi_fragment_cache_path(param)
+ path = foi_fragment_cache_part_path(param)
+ path = "/views" + path
+ return File.join(self.cache_store.cache_path, path)
+ end
+ def foi_fragment_cache_all_for_request(info_request)
+ first_three_digits = info_request.id.to_s()[0..2]
+ path = "views/request/#{first_three_digits}/#{info_request.id}"
+ return File.join(self.cache_store.cache_path, path)
+ end
+
private
# Check the user is logged in
@@ -184,6 +196,16 @@ class ApplicationController < ActionController::Base
end
end
+ #
+ def check_read_only
+ read_only = MySociety::Config.get('READ_ONLY')
+ if !read_only.empty?
+ flash[:notice] = "<p>WhatDoTheyKnow is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.</p> <p>" + read_only + "</p>"
+ redirect_to frontpage_url
+ end
+
+ end
+
# For administration interface, return display name of authenticated user
def admin_http_auth_user
# This needs special magic in mongrel: http://www.ruby-forum.com/topic/83067
diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb
index dfa31f9ef..d5f8f89fb 100644
--- a/app/controllers/comment_controller.rb
+++ b/app/controllers/comment_controller.rb
@@ -7,6 +7,7 @@
# $Id: comment_controller.rb,v 1.9 2009-03-09 01:17:04 francis Exp $
class CommentController < ApplicationController
+ before_filter :check_read_only, :only => [ :new ]
def new
if params[:type] == 'request'
diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb
index 5c4103616..efab26fad 100644
--- a/app/controllers/general_controller.rb
+++ b/app/controllers/general_controller.rb
@@ -7,7 +7,7 @@
#
# $Id: general_controller.rb,v 1.57 2009-10-03 10:23:43 francis Exp $
-require 'xmlsimple'
+require 'lib/xmlsimple'
require 'open-uri'
class GeneralController < ApplicationController
diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb
index 1ca82e463..591081fe9 100644
--- a/app/controllers/public_body_controller.rb
+++ b/app/controllers/public_body_controller.rb
@@ -6,6 +6,8 @@
#
# $Id: public_body_controller.rb,v 1.8 2009-09-14 13:27:00 francis Exp $
+require 'csv'
+
class PublicBodyController < ApplicationController
# XXX tidy this up with better error messages, and a more standard infrastructure for the redirect to canonical URL
def show
@@ -93,5 +95,51 @@ class PublicBodyController < ApplicationController
cache_in_squid
end
+
+ # Used so URLs like /local/islington work, for use e.g. writing to a local paper.
+ def list_redirect
+ @tag = params[:tag]
+ redirect_to list_public_bodies_url(:tag => @tag)
+ end
+
+ def list_all_csv
+ public_bodies = PublicBody.find(:all, :order => 'url_name')
+ report = StringIO.new
+ CSV::Writer.generate(report, ',') do |title|
+ title << [
+ 'Name',
+ 'Short name',
+ # deliberately not including 'Request email'
+ 'URL name',
+ 'Tags',
+ 'Home page',
+ 'Publication scheme',
+ 'Charity number',
+ 'Created at',
+ 'Updated at',
+ 'Version',
+ ]
+ public_bodies.each do |public_body|
+ title << [
+ public_body.name,
+ public_body.short_name,
+ # DO NOT include request_email (we don't want to make it
+ # easy to spam all authorities with requests)
+ public_body.url_name,
+ public_body.tag_string,
+ public_body.calculated_home_page,
+ public_body.publication_scheme,
+ public_body.charity_number,
+ public_body.created_at,
+ public_body.updated_at,
+ public_body.version,
+ ]
+ end
+ end
+ report.rewind
+ send_data(report.read, :type=> 'text/csv; charset=utf-8; header=present',
+ :filename => 'all-authorities.csv',
+ :disposition =>'attachment', :encoding => 'utf8')
+ end
end
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 2606a9609..0664093c3 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -7,6 +7,7 @@
# $Id: request_controller.rb,v 1.192 2009-10-19 19:26:40 francis Exp $
class RequestController < ApplicationController
+ before_filter :check_read_only, :only => [ :new, :show_response, :describe_state ]
def show
# Look up by old style numeric identifiers
@@ -64,11 +65,26 @@ class RequestController < ApplicationController
@last_response = @info_request.get_last_response
end
+ # Extra info about a request, such as event history
+ def details
+ @info_request = InfoRequest.find_by_url_title(params[:url_title])
+ if !@info_request.user_can_view?(authenticated_user)
+ render :template => 'request/hidden', :status => 410 # gone
+ return
+ end
+
+ @columns = ['id', 'event_type', 'created_at', 'described_state', 'last_described_at', 'calculated_state' ]
+ end
+
# Requests similar to this one
def similar
@per_page = 25
@page = (params[:page] || "1").to_i
@info_request = InfoRequest.find_by_url_title(params[:url_title])
+ if !@info_request.user_can_view?(authenticated_user)
+ render :template => 'request/hidden', :status => 410 # gone
+ return
+ end
@xapian_object = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events,
:offset => (@page - 1) * @per_page, :limit => @per_page, :collapse_by_prefix => 'request_collapse')
@@ -148,27 +164,27 @@ class RequestController < ApplicationController
# First time we get to the page, just display it
if params[:submitted_new_request].nil? || params[:reedit]
# Read parameters in - public body must be passed in
- if params[:public_body_id]
- params[:info_request] = { :public_body_id => params[:public_body_id] }
+ params[:info_request] = { :public_body_id => params[:public_body_id] } if !params[:info_request]
+ if !params[:info_request][:public_body_id]
+ redirect_to frontpage_url
+ return
end
@info_request = InfoRequest.new(params[:info_request])
params[:info_request_id] = @info_request.id
+ params[:outgoing_message] = {} if !params[:outgoing_message]
+ params[:outgoing_message][:info_request] = @info_request
@outgoing_message = OutgoingMessage.new(params[:outgoing_message])
@outgoing_message.set_signature_name(@user.name) if !@user.nil?
- if @info_request.public_body.nil?
- redirect_to frontpage_url
- else
- if @info_request.public_body.is_requestable?
- render :action => 'new'
+ if @info_request.public_body.is_requestable?
+ render :action => 'new'
+ else
+ if @info_request.public_body.not_requestable_reason == 'bad_contact'
+ render :action => 'new_bad_contact'
else
- if @info_request.public_body.not_requestable_reason == 'bad_contact'
- render :action => 'new_bad_contact'
- else
- # if not requestable because defunct or not_apply, redirect to main page
- # (which doesn't link to the /new/ URL)
- redirect_to public_body_url(@info_request.public_body)
- end
+ # if not requestable because defunct or not_apply, redirect to main page
+ # (which doesn't link to the /new/ URL)
+ redirect_to public_body_url(@info_request.public_body)
end
end
return
@@ -318,11 +334,14 @@ class RequestController < ApplicationController
# Display advice for requester on what to do next, as appropriate
if @info_request.calculate_status == 'waiting_response'
- flash[:notice] = "<p>Thank you! Hopefully your wait isn't too long.</p> <p>By law, you should get a response promptly, and normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>"
+ flash[:notice] = "<p>Thank you! Hopefully your wait isn't too long.</p> <p>By law, you should get a response promptly, and " + (@info_request.public_body.is_school? ? "in term time" : "") + " normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>"
redirect_to request_url(@info_request)
elsif @info_request.calculate_status == 'waiting_response_overdue'
- flash[:notice] = "<p>Thank you! Hope you don't have to wait much longer.</p> <p>By law, you should have got a response promptly, and normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>"
+ flash[:notice] = "<p>Thank you! Hope you don't have to wait much longer.</p> <p>By law, you should have got a response promptly, and " + (@info_request.public_body.is_school? ? "in term time" : "") + " normally before the end of <strong>" + simple_date(@info_request.date_response_required_by) + "</strong>.</p>"
redirect_to request_url(@info_request)
+ elsif @info_request.calculate_status == 'waiting_response_very_overdue'
+ flash[:notice] = "<p>Thank you! Your request is long overdue, by more than 40 working days. Most requests should be answered within 20 working days. You might like to complain about this, see below.</p>"
+ redirect_to unhappy_url(@info_request)
elsif @info_request.calculate_status == 'not_held'
flash[:notice] = "<p>Thank you! Here are some ideas on what to do next:</p>
<ul>
@@ -337,7 +356,7 @@ class RequestController < ApplicationController
"
redirect_to request_url(@info_request)
elsif @info_request.calculate_status == 'rejected'
- flash[:notice] = "Oh no! Sorry to hear that your request was rejected. Here is what to do now."
+ flash[:notice] = "Oh no! Sorry to hear that your request was refused. Here is what to do now."
redirect_to unhappy_url(@info_request)
elsif @info_request.calculate_status == 'successful'
flash[:notice] = "<p>We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.</p><p>If you found WhatDoTheyKnow useful, <a href=\"http://www.mysociety.org/donate/\">make a donation</a> to the charity which runs it.</p>"
@@ -360,8 +379,8 @@ class RequestController < ApplicationController
flash[:notice] = "Please use the form below to tell us more."
redirect_to help_general_url(:action => 'contact')
elsif @info_request.calculate_status == 'user_withdrawn'
- flash[:notice] = "Thanks for letting us know that you've withdrawn your request. Please add an annotation below to let other people know why you withdrew it."
- redirect_to request_url(@info_request)
+ flash[:notice] = "If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn."
+ redirect_to respond_to_last_url(@info_request)
else
raise "unknown calculate_status " + @info_request.calculate_status
end
@@ -519,7 +538,7 @@ class RequestController < ApplicationController
# Test for hidden
incoming_message = IncomingMessage.find(params[:incoming_message_id])
if !incoming_message.info_request.user_can_view?(authenticated_user)
- render :template => 'request/hidden'
+ render :template => 'request/hidden', :status => 410 # gone
end
end
@@ -527,8 +546,10 @@ class RequestController < ApplicationController
around_filter :cache_attachments, :only => [ :get_attachment, :get_attachment_as_html ]
def cache_attachments
key = params.merge(:only_path => true)
- if cached = read_fragment(key)
- #if cached = 'zzz***zzz'
+ key_path = foi_fragment_cache_path(key)
+
+ if File.exists?(key_path)
+ cached = File.read(key_path)
IncomingMessage # load global filename_to_mimetype XXX should move filename_to_mimetype to proper namespace
response.content_type = filename_to_mimetype(params[:file_name].join("/")) or 'application/octet-stream'
render_for_text(cached)
@@ -537,7 +558,14 @@ class RequestController < ApplicationController
yield
- write_fragment(key, response.body)
+ # write it to the fileystem ourselves, so is just a plain file. (The
+ # various fragment cache functions using Ruby Marshall to write the file
+ # which adds a header, so isnt compatible with images that have been
+ # extracted elsewhere from PDFs)
+ FileUtils.mkdir_p(File.dirname(key_path))
+ File.atomic_write(key_path) do |f|
+ f.write(response.body)
+ end
end
def get_attachment
@@ -558,14 +586,16 @@ class RequestController < ApplicationController
# images made during conversion (e.g. images in PDF files) are put in the cache directory, so
# the same cache code in cache_attachments above will display them.
- image_dir = File.dirname(ActionController::Base.cache_store.cache_path + "/views" + url_for(params.merge(:only_path => true)))
+ key = params.merge(:only_path => true)
+ key_path = foi_fragment_cache_path(key)
+ image_dir = File.dirname(key_path)
FileUtils.mkdir_p(image_dir)
- html = @attachment.body_as_html(image_dir)
+ html, wrapper_id = @attachment.body_as_html(image_dir)
view_html_stylesheet = render_to_string :partial => "request/view_html_stylesheet"
html.sub!(/<head>/i, "<head>" + view_html_stylesheet)
- html.sub!(/<body[^>]*>/i, '<body><prefix-here><div id="wrapper"><div id="view_html_content">' + view_html_stylesheet)
- html.sub!(/<\/body[^>]*>/i, '</div></div></body>' + view_html_stylesheet)
+ html.sub!(/<body[^>]*>/i, '<body><prefix-here><div id="' + wrapper_id + '"><div id="view_html_content">')
+ html.sub!(/<\/body[^>]*>/i, '</div></div></body>')
view_html_prefix = render_to_string :partial => "request/view_html_prefix"
html.sub!("<prefix-here>", view_html_prefix)
@@ -595,6 +625,7 @@ class RequestController < ApplicationController
raise "internal error, pre-auth filter should have caught this" if !@info_request.user_can_view?(authenticated_user)
@attachment = IncomingMessage.get_attachment_by_url_part_number(@incoming_message.get_attachments_for_display, @part_number)
+ raise "attachment not found part number " + @part_number.to_s + " incoming_message " + @incoming_message.id.to_s if @attachment.nil?
# check filename in URL matches that in database (use a censor rule if you want to change a filename)
raise "please use same filename as original file has, display: '" + @attachment.display_filename + "' old_display: '" + @attachment.old_display_filename + "' original: '" + @original_filename + "'" if @attachment.display_filename != @original_filename && @attachment.old_display_filename != @original_filename
@@ -631,7 +662,7 @@ class RequestController < ApplicationController
if params[:submitted_upload_response]
file_name = nil
file_content = nil
- if params[:file_1].class.to_s == "ActionController::UploadedTempfile"
+ if !params[:file_1].nil?
file_name = params[:file_1].original_filename
file_content = params[:file_1].read
end
diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb
index bc117ce2e..b3f9511b5 100644
--- a/app/controllers/user_controller.rb
+++ b/app/controllers/user_controller.rb
@@ -147,10 +147,13 @@ class UserController < ApplicationController
end
# Logout form
- def signout
+ def _do_signout
session[:user_id] = nil
session[:user_circumstance] = nil
session[:remember_me] = false
+ end
+ def signout
+ self._do_signout
if params[:r]
redirect_to params[:r]
else
@@ -159,24 +162,24 @@ class UserController < ApplicationController
end
# Change password (XXX and perhaps later email) - requires email authentication
- def signchange
+ def signchangepassword
if @user and ((not session[:user_circumstance]) or (session[:user_circumstance] != "change_password"))
# Not logged in via email, so send confirmation
- params[:submitted_signchange_send_confirm] = true
- params[:signchange] = { :email => @user.email }
+ params[:submitted_signchangepassword_send_confirm] = true
+ params[:signchangepassword] = { :email => @user.email }
end
- if params[:submitted_signchange_send_confirm]
+ if params[:submitted_signchangepassword_send_confirm]
# They've entered the email, check it is OK and user exists
- if not MySociety::Validate.is_valid_email(params[:signchange][:email])
+ if not MySociety::Validate.is_valid_email(params[:signchangepassword][:email])
flash[:error] = "That doesn't look like a valid email address. Please check you have typed it correctly."
- render :action => 'signchange_send_confirm'
+ render :action => 'signchangepassword_send_confirm'
return
end
- user_signchange = User.find_user_by_email(params[:signchange][:email])
- if user_signchange
- # Send email with login link to go to signchange page
- url = signchange_url
+ user_signchangepassword = User.find_user_by_email(params[:signchangepassword][:email])
+ if user_signchangepassword
+ # Send email with login link to go to signchangepassword page
+ url = signchangepassword_url
if params[:pretoken]
url += "?pretoken=" + params[:pretoken]
end
@@ -188,27 +191,27 @@ class UserController < ApplicationController
},
:circumstance => "change_password" # special login that lets you change your password
)
- post_redirect.user = user_signchange
+ post_redirect.user = user_signchangepassword
post_redirect.save!
url = confirm_url(:email_token => post_redirect.email_token)
- UserMailer.deliver_confirm_login(user_signchange, post_redirect.reason_params, url)
+ UserMailer.deliver_confirm_login(user_signchangepassword, post_redirect.reason_params, url)
else
# User not found, but still show confirm page to not leak fact user exists
end
- render :action => 'signchange_confirm'
+ render :action => 'signchangepassword_confirm'
elsif not @user
# Not logged in, prompt for email
- render :action => 'signchange_send_confirm'
+ render :action => 'signchangepassword_send_confirm'
else
# Logged in via special email change password link, so can offer form to change password
raise "internal error" unless (session[:user_circumstance] == "change_password")
- if params[:submitted_signchange_password]
+ if params[:submitted_signchangepassword_do]
@user.password = params[:user][:password]
@user.password_confirmation = params[:user][:password_confirmation]
if not @user.valid?
- render :action => 'signchange'
+ render :action => 'signchangepassword'
else
@user.save!
flash[:notice] = "Your password has been changed."
@@ -220,11 +223,71 @@ class UserController < ApplicationController
end
end
else
- render :action => 'signchange'
+ render :action => 'signchangepassword'
end
end
end
+ # Change your email
+ def signchangeemail
+ if not authenticated?(
+ :web => "To change your email address used on WhatDoTheyKnow.com",
+ :email => "Then you can change your email address used on WhatDoTheyKnow.com",
+ :email_subject => "Change your email address used on WhatDoTheyKnow.com"
+ )
+ # "authenticated?" has done the redirect to signin page for us
+ return
+ end
+
+ if !params[:submitted_signchangeemail_do]
+ render :action => 'signchangeemail'
+ return
+ end
+
+ @signchangeemail = ChangeEmailValidator.new(params[:signchangeemail])
+ @signchangeemail.logged_in_user = @user
+
+ if !@signchangeemail.valid?
+ render :action => 'signchangeemail'
+ return
+ end
+
+ # if new email already in use, send email there saying what happened
+ user_alreadyexists = User.find_user_by_email(@signchangeemail.new_email)
+ if user_alreadyexists
+ UserMailer.deliver_changeemail_already_used(@user.email, @signchangeemail.new_email)
+ # it is important this screen looks the same as the one below, so
+ # you can't change to someone's email in order to tell if they are
+ # registered with that email on the site
+ render :action => 'signchangeemail_confirm'
+ return
+ end
+
+ # if not already, send a confirmation link to the new email address which logs
+ # them into the old email's user account, but with special user_circumstance
+ if (not session[:user_circumstance]) or (session[:user_circumstance] != "change_email")
+ post_redirect = PostRedirect.new(:uri => signchangeemail_url(), :post_params => params,
+ :circumstance => "change_email" # special login that lets you change your email
+ )
+ post_redirect.user = @user
+ post_redirect.save!
+
+ url = confirm_url(:email_token => post_redirect.email_token)
+ UserMailer.deliver_changeemail_confirm(@user, @signchangeemail.new_email, url)
+ # it is important this screen looks the same as the one above, so
+ # you can't change to someone's email in order to tell if they are
+ # registered with that email on the site
+ render :action => 'signchangeemail_confirm'
+ return
+ end
+
+ # circumstance is 'change_email', so can actually change the email
+ @user.email = @signchangeemail.new_email
+ @user.save!
+ flash[:notice] = "You have now changed your email address used on WhatDoTheyKnow.com"
+ redirect_to user_url(@user)
+ end
+
# Send a message to another user
def contact
@recipient_user = User.find(params[:id])
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 7cc0b0e5d..08908abee 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -59,5 +59,11 @@ module ApplicationHelper
t = highlight_words(t, words, html)
return t
end
+
+ # Use our own algorithm for finding path of cache
+ def foi_cache(name = {}, options = nil, &block)
+ name = @controller.foi_fragment_cache_part_path(name)
+ @controller.fragment_for(output_buffer, name, options, &block)
+ end
end
diff --git a/app/helpers/link_to_helper.rb b/app/helpers/link_to_helper.rb
index 6ee0edb2b..aa63ef65d 100644
--- a/app/helpers/link_to_helper.rb
+++ b/app/helpers/link_to_helper.rb
@@ -33,6 +33,10 @@ module LinkToHelper
return similar_request_url(:url_title => info_request.url_title, :only_path => true)
end
+ def request_details_url(info_request)
+ return details_request_url(:url_title => info_request.url_title, :only_path => true)
+ end
+
# Incoming / outgoing messages
def incoming_message_url(incoming_message)
return request_url(incoming_message.info_request)+"#incoming-"+incoming_message.id.to_s
diff --git a/app/models/change_email_validator.rb b/app/models/change_email_validator.rb
new file mode 100644
index 000000000..ff7f2f931
--- /dev/null
+++ b/app/models/change_email_validator.rb
@@ -0,0 +1,42 @@
+# models/changeemail_validator.rb:
+# Validates email change form submissions.
+#
+# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved.
+# Email: francis@mysociety.org; WWW: http://www.mysociety.org/
+#
+# $Id: contact_validator.rb,v 1.32 2009-09-17 21:10:05 francis Exp $
+
+class ChangeEmailValidator < ActiveRecord::BaseWithoutTable
+ strip_attributes!
+
+ column :old_email, :string
+ column :new_email, :string
+ column :password, :string
+
+ attr_accessor :logged_in_user
+
+ validates_presence_of :old_email, :message => "^Please enter your old email address"
+ validates_presence_of :new_email, :message => "^Please enter your new email address"
+ validates_presence_of :password, :message => "^Please enter your password"
+
+ def validate
+ if !self.old_email.blank? && !MySociety::Validate.is_valid_email(self.old_email)
+ errors.add(:old_email, "doesn't look like a valid address")
+ end
+
+ if !errors[:old_email]
+ if self.old_email.downcase != self.logged_in_user.email.downcase
+ errors.add(:old_email, "address isn't the same as the address of the account you are logged in with")
+ elsif !self.logged_in_user.has_this_password?(self.password)
+ if !errors[:password]
+ errors.add(:password, "is not correct")
+ end
+ end
+ end
+
+ if !self.new_email.blank? && !MySociety::Validate.is_valid_email(self.new_email)
+ errors.add(:new_email, "doesn't look like a valid address")
+ end
+ end
+
+end
diff --git a/app/models/comment.rb b/app/models/comment.rb
index 0da6c1ce3..4e9976373 100644
--- a/app/models/comment.rb
+++ b/app/models/comment.rb
@@ -45,7 +45,7 @@ class Comment < ActiveRecord::Base
read_attribute(:body)
end
- # So when made invisble it vanishes
+ # So when takes changes it updates, or when made invisble it vanishes
after_save :event_xapian_update
def event_xapian_update
for event in self.info_request_events
diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb
index 2348c17b5..316f2683a 100644
--- a/app/models/incoming_message.rb
+++ b/app/models/incoming_message.rb
@@ -29,6 +29,8 @@ require 'htmlentities'
require 'rexml/document'
require 'zip/zip'
require 'mahoro'
+require 'mapi/msg'
+require 'mapi/convert'
# Monkeypatch! Adding some extra members to store extra info in.
module TMail
@@ -51,6 +53,9 @@ $file_extension_to_mime_type = {
"xlsx" => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
"ppt" => 'application/vnd.ms-powerpoint',
"pptx" => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ "oft" => 'application/vnd.ms-outlook',
+ "msg" => 'application/vnd.ms-outlook',
+ "tnef" => 'application/ms-tnef',
"tif" => 'image/tiff',
"gif" => 'image/gif',
"jpg" => 'image/jpeg', # XXX add jpeg
@@ -303,10 +308,54 @@ class FOIAttachment
end
end
+ # Whether this type has a "View as HTML"
+ def has_body_as_html?
+ if self.content_type == 'text/plain'
+ return true
+ elsif self.content_type == 'application/vnd.ms-word'
+ return true
+ elsif self.content_type == 'application/vnd.ms-excel'
+ return true
+ elsif self.content_type == 'application/pdf'
+ return true
+ elsif self.content_type == 'application/rtf'
+ return true
+ end
+ return false
+ end
+
+ # Name of type of attachment type - only valid for things that has_body_as_html?
+ def name_of_content_type
+ if self.content_type == 'text/plain'
+ return "Text file"
+ elsif self.content_type == 'application/vnd.ms-word'
+ return "Word document"
+ elsif self.content_type == 'application/vnd.ms-excel'
+ return "Excel spreadsheet"
+ elsif self.content_type == 'application/pdf'
+ return "PDF file"
+ elsif self.content_type == 'application/rtf'
+ return "RTF file"
+ end
+ end
+
# For "View as HTML" of attachment
def body_as_html(dir)
html = nil
+ wrapper_id = "wrapper"
+
+ # simple cases, can never fail
+ if self.content_type == 'text/plain'
+ text = self.body.strip
+ text = CGI.escapeHTML(text)
+ text = MySociety::Format.make_clickable(text)
+ html = text.gsub(/\n/, '<br>')
+ return "<html><head></head><body>" + html + "</body></html>", wrapper_id
+ end
+ # the extractions will also produce image files, which go in the
+ # current directory, so change to the directory the function caller
+ # wants everything in
Dir.chdir(dir) do
tempfile = Tempfile.new('foiextract', '.')
tempfile.print self.body
@@ -317,10 +366,22 @@ class FOIAttachment
system("/usr/bin/wvHtml --charset=UTF-8 " + tempfile.path + " " + tempfile.path + ".html")
html = File.read(tempfile.path + ".html")
File.unlink(tempfile.path + ".html")
+ elsif self.content_type == 'application/vnd.ms-excel'
+ # Don't colorise, e.g. otherwise this one comes out with white
+ # text which is nasty:
+ # http://www.whatdotheyknow.com/request/30485/response/74705/attach/html/2/Empty%20premises%20Sefton.xls.html
+ IO.popen("/usr/bin/xlhtml -nc -a " + tempfile.path + "", "r") do |child|
+ html = child.read()
+ wrapper_id = "wrapper_xlhtml"
+ end
elsif self.content_type == 'application/pdf'
IO.popen("/usr/bin/pdftohtml -nodrm -zoom 1.0 -stdout -enc UTF-8 -noframes " + tempfile.path + "", "r") do |child|
html = child.read()
end
+ elsif self.content_type == 'application/rtf'
+ IO.popen("/usr/bin/unrtf --html " + tempfile.path + "", "r") do |child|
+ html = child.read()
+ end
else
raise "No HTML conversion available for type " + self.content_type
end
@@ -341,30 +402,12 @@ class FOIAttachment
body_without_tags = body.gsub(/\s+/,"").gsub(/\<[^\>]*\>/, "")
contains_images = html.match(/<img/mi) ? true : false
if !$?.success? || html.size == 0 || (body_without_tags.size == 0 && !contains_images)
- return "<html><head></head><body><p>Sorry, the conversion to HTML failed. Please use the download link at the top right.</p></body></html>"
+ return "<html><head></head><body><p>Sorry, the conversion to HTML failed. Please use the download link at the top right.</p></body></html>", wrapper_id
end
- return html
- end
-
- # Whether this type has a "View as HTML"
- def has_body_as_html?
- if self.content_type == 'application/vnd.ms-word'
- return true
- elsif self.content_type == 'application/pdf'
- return true
- end
- return false
+ return html, wrapper_id
end
- # Name of type of attachment type - only valid for things that has_body_as_html?
- def name_of_content_type
- if self.content_type == 'application/vnd.ms-word'
- return "Word document"
- elsif self.content_type == 'application/pdf'
- return "PDF file"
- end
- end
end
class IncomingMessage < ActiveRecord::Base
@@ -419,20 +462,30 @@ class IncomingMessage < ActiveRecord::Base
_count_parts_recursive(p)
end
else
- if part.content_type == 'message/rfc822'
- # An email attached as text
- # e.g. http://www.whatdotheyknow.com/request/64/response/102
- begin
+ part_filename = TMail::Mail.get_part_file_name(part)
+ begin
+ if part.content_type == 'message/rfc822'
+ # An email attached as text
+ # e.g. http://www.whatdotheyknow.com/request/64/response/102
part.rfc822_attachment = TMail::Mail.parse(part.body)
- rescue
- # If attached mail doesn't parse, treat it as text part
- part.rfc822_attachment = nil
- @count_parts_count += 1
- part.url_part_number = @count_parts_count
- else
- _count_parts_recursive(part.rfc822_attachment)
+ elsif part.content_type == 'application/vnd.ms-outlook' || part_filename && filename_to_mimetype(part_filename) == 'application/vnd.ms-outlook'
+ # An email attached as an Outlook file
+ # e.g. http://www.whatdotheyknow.com/request/chinese_names_for_british_politi
+ msg = Mapi::Msg.open(StringIO.new(part.body))
+ part.rfc822_attachment = TMail::Mail.parse(msg.to_mime.to_s)
+ elsif part.content_type == 'application/ms-tnef'
+ # A set of attachments in a TNEF file
+ part.rfc822_attachment = TNEF.as_tmail(part.body)
end
+ rescue
+ # If attached mail doesn't parse, treat it as text part
+ part.rfc822_attachment = nil
else
+ unless part.rfc822_attachment.nil?
+ _count_parts_recursive(part.rfc822_attachment)
+ end
+ end
+ if part.rfc822_attachment.nil?
@count_parts_count += 1
part.url_part_number = @count_parts_count
end
@@ -486,7 +539,7 @@ class IncomingMessage < ActiveRecord::Base
uncompressed_text = child.read()
end
# if we managed to uncompress the PDF...
- if !uncompressed_text.nil?
+ if !uncompressed_text.nil? && !uncompressed_text.empty?
# then censor stuff (making a copy so can compare again in a bit)
censored_uncompressed_text = uncompressed_text.dup
self._binary_mask_stuff_internal!(censored_uncompressed_text)
@@ -499,7 +552,7 @@ class IncomingMessage < ActiveRecord::Base
child.close_write()
recompressed_text = child.read()
end
- if !recompressed_text.nil?
+ if !recompressed_text.nil? && !recompressed_text.empty?
text[0..-1] = recompressed_text # [0..-1] makes it change the 'text' string in place
end
end
@@ -707,15 +760,22 @@ class IncomingMessage < ActiveRecord::Base
if curr_mail.sub_type == 'alternative'
# Choose best part from alternatives
best_part = nil
+ # Take the last text/plain one, or else the first one
curr_mail.parts.each do |m|
- # Take the first one, or the last text/plain one
- # XXX - could do better!
if not best_part
best_part = m
elsif m.content_type == 'text/plain'
best_part = m
end
end
+ # Take an HTML one as even higher priority. (They tend
+ # to render better than text/plain, e.g. don't wrap links here:
+ # http://www.whatdotheyknow.com/request/amount_and_cost_of_freedom_of_in#incoming-72238 )
+ curr_mail.parts.each do |m|
+ if m.content_type == 'text/html'
+ best_part = m
+ end
+ end
leaves_found += _get_attachment_leaves_recursive(best_part, within_rfc822_attachment)
else
# Add all parts
@@ -724,6 +784,11 @@ class IncomingMessage < ActiveRecord::Base
end
end
else
+ # XXX Yuck. this section alters various content_type's. That puts
+ # it into conflict with ensure_parts_counted which it has to be
+ # called both before and after. It will fail with cases of
+ # attachments of attachments etc.
+
# Don't allow nil content_types
if curr_mail.content_type.nil?
curr_mail.content_type = 'application/octet-stream'
@@ -746,9 +811,16 @@ class IncomingMessage < ActiveRecord::Base
curr_mail.content_type = 'text/plain'
end
end
+ if curr_mail.content_type == 'application/vnd.ms-outlook' || curr_mail.content_type == 'application/ms-tnef'
+ ensure_parts_counted # fills in rfc822_attachment variable
+ if curr_mail.rfc822_attachment.nil?
+ # Attached mail didn't parse, so treat as binary
+ curr_mail.content_type = 'application/octet-stream'
+ end
+ end
- # If the part is an attachment of email in text form
- if curr_mail.content_type == 'message/rfc822'
+ # If the part is an attachment of email
+ if curr_mail.content_type == 'message/rfc822' || curr_mail.content_type == 'application/vnd.ms-outlook' || curr_mail.content_type == 'application/ms-tnef'
ensure_parts_counted # fills in rfc822_attachment variable
leaves_found += _get_attachment_leaves_recursive(curr_mail.rfc822_attachment, curr_mail.rfc822_attachment)
else
@@ -863,9 +935,13 @@ class IncomingMessage < ActiveRecord::Base
def get_main_body_text_part
leaves = get_attachment_leaves
- # Find first part which is text/plain
+ # Find first part which is text/plain or text/html
+ # (We have to include HTML, as increasingly there are mail clients that
+ # include no text alternative for the main part, and we don't want to
+ # instead use the first text attachment
+ # e.g. http://www.whatdotheyknow.com/request/list_of_public_authorties)
leaves.each do |p|
- if p.content_type == 'text/plain'
+ if p.content_type == 'text/plain' or p.content_type == 'text/html'
return p
end
end
@@ -898,7 +974,7 @@ class IncomingMessage < ActiveRecord::Base
# e.g. for https://secure.mysociety.org/admin/foi/request/show_raw_email/24550
main_part = get_main_body_text_part
if main_part.nil?
- return
+ return []
end
text = main_part.body
@@ -936,10 +1012,13 @@ class IncomingMessage < ActiveRecord::Base
# Returns all attachments for use in display code
# XXX is this called multiple times and should be cached?
def get_attachments_for_display
- ensure_parts_counted
-
main_part = get_main_body_text_part
leaves = get_attachment_leaves
+
+ # XXX we have to call ensure_parts_counted after get_attachment_leaves
+ # which is really messy.
+ ensure_parts_counted
+
attachments = []
for leaf in leaves
if leaf != main_part
@@ -965,7 +1044,12 @@ class IncomingMessage < ActiveRecord::Base
headers = ""
for header in [ 'Date', 'Subject', 'From', 'To', 'Cc' ]
if leaf.within_rfc822_attachment.header.include?(header.downcase)
- headers = headers + header + ": " + leaf.within_rfc822_attachment.header[header.downcase].to_s + "\n"
+ header_value = leaf.within_rfc822_attachment.header[header.downcase]
+ # Example message which has a blank Date header:
+ # http://www.whatdotheyknow.com/request/30747/response/80253/attach/html/17/Common%20Purpose%20Advisory%20Group%20Meeting%20Tuesday%202nd%20March.txt.html
+ if !header_value.blank?
+ headers = headers + header + ": " + header_value.to_s + "\n"
+ end
end
end
# XXX call _convert_part_body_to_text here, but need to get charset somehow
@@ -1099,11 +1183,14 @@ class IncomingMessage < ActiveRecord::Base
File.unlink(tempfile.path + ".txt")
end
elsif content_type == 'application/rtf'
+ # catdoc on RTF prodcues less comments and extra bumf than --text option to unrtf
IO.popen("/usr/bin/catdoc " + tempfile.path, "r") do |child|
text += child.read() + "\n\n"
end
elsif content_type == 'text/html'
- IO.popen("/usr/bin/lynx -display_charset=UTF-8 -force_html -dump " + tempfile.path, "r") do |child|
+ # lynx wordwraps links in its output, which then don't get formatted properly
+ # by WhatDoTheyKnow. We use elinks instead, which doesn't do that.
+ IO.popen("/usr/bin/elinks -dump-charset utf-8 -force-html -dump " + tempfile.path, "r") do |child|
text += child.read() + "\n\n"
end
elsif content_type == 'application/vnd.ms-excel'
@@ -1273,7 +1360,7 @@ class IncomingMessage < ActiveRecord::Base
prefix = email
prefix =~ /^(.*)@/
prefix = $1
- if !prefix.nil? && prefix.downcase.match(/^(postmaster|mailer-daemon|auto_reply|donotreply)$/)
+ if !prefix.nil? && prefix.downcase.match(/^(postmaster|mailer-daemon|auto_reply|donotreply|no-reply)$/)
return false
end
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index 7cee3fe1c..f6eec0601 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -56,7 +56,7 @@ class InfoRequest < ActiveRecord::Base
'waiting_clarification',
'gone_postal',
'not_held',
- 'rejected',
+ 'rejected', # this is called 'refused' in UK FOI law and the user interface, but 'rejected' internally for historic reasons
'successful',
'partially_successful',
'internal_review',
@@ -83,7 +83,7 @@ class InfoRequest < ActiveRecord::Base
'authority_only', # only people from authority domains
'nobody'
]
- # what to do with rejected new responses
+ # what to do with refused new responses
validates_inclusion_of :handle_rejected_responses, :in => [
'bounce', # return them to sender
'holding_pen', # put them in the holding pen
@@ -95,6 +95,9 @@ class InfoRequest < ActiveRecord::Base
if !self.title.nil? && !MySociety::Validate.uses_mixed_capitals(self.title, 10)
errors.add(:title, '^Please write the summary using a mixture of capital and lower case letters. This makes it easier for others to read.')
end
+ if !self.title.nil? && title.size > 200
+ errors.add(:title, '^Please keep the summary short, like in the subject of an email. You can use a phrase, rather than a full sentence.')
+ end
end
OLD_AGE_IN_DAYS = 21.days
@@ -132,7 +135,7 @@ class InfoRequest < ActiveRecord::Base
# we update index for every event. Also reindex if prominence changes.
after_update :reindex_some_request_events
def reindex_some_request_events
- if self.changes.include?('url_title') || self.changes.include?('prominence')
+ if self.changes.include?('url_title') || self.changes.include?('prominence') || self.changes.include?('user_id')
self.reindex_request_events
end
end
@@ -199,6 +202,8 @@ public
# Subject lines for emails about the request
def email_subject_request
+ # XXX pull out this general_register_office specialisation
+ # into some sort of separate jurisdiction dependent file
if self.public_body.url_name == 'general_register_office'
# without GQ in the subject, you just get an auto response
self.law_used_full + ' request GQ - ' + self.title
@@ -206,11 +211,15 @@ public
self.law_used_full + ' request - ' + self.title
end
end
- def email_subject_followup
- if self.public_body.url_name == 'general_register_office'
- 'Re: ' + self.law_used_full + ' request GQ - ' + self.title
+ def email_subject_followup(incoming_message = nil)
+ if incoming_message.nil? || !incoming_message.valid_to_reply_to?
+ 'Re: ' + self.email_subject_request
else
- 'Re: ' + self.law_used_full + ' request - ' + self.title
+ if incoming_message.mail.subject.match(/^Re:/i)
+ incoming_message.mail.subject
+ else
+ 'Re: ' + incoming_message.mail.subject
+ end
end
end
@@ -487,10 +496,13 @@ public
# self.described_state, can take these two values:
# waiting_classification
# waiting_response_overdue
+ # waiting_response_very_overdue
def calculate_status
return 'waiting_classification' if self.awaiting_description
return described_state unless self.described_state == "waiting_response"
# Compare by date, so only overdue on next day, not if 1 second late
+ return 'waiting_response_very_overdue' if
+ Time.now.strftime("%Y-%m-%d") > self.date_very_overdue_after.strftime("%Y-%m-%d")
return 'waiting_response_overdue' if
Time.now.strftime("%Y-%m-%d") > self.date_response_required_by.strftime("%Y-%m-%d")
return 'waiting_response'
@@ -568,24 +580,35 @@ public
end
end
if last_sent.nil?
- raise "internal error, date_response_required_by gets nil for request " + self.id.to_s + " outgoing messages count " + self.outgoing_messages.size.to_s + " all events: " + self.info_request_events.to_yaml
+ raise "internal error, last_event_forming_initial_request gets nil for request " + self.id.to_s + " outgoing messages count " + self.outgoing_messages.size.to_s + " all events: " + self.info_request_events.to_yaml
end
return last_sent
end
+ # The last time that the initial request was sent/resent
+ def date_initial_request_last_sent_at
+ last_sent = last_event_forming_initial_request
+ return last_sent.outgoing_message.last_sent_at
+ end
# How do we cope with case where extra info was required from the requester
# by the public body in order to fulfill the request, as per sections 1(3)
# and 10(6b) ? For clarifications this is covered by
# last_event_forming_initial_request. There may be more obscure
# things, e.g. fees, not properly covered.
def date_response_required_by
- last_sent = last_event_forming_initial_request
- return Holiday.due_date_from(last_sent.outgoing_message.last_sent_at, 20)
+ return Holiday.due_date_from(self.date_initial_request_last_sent_at, 20)
end
-
- # Are we more than 20 working days overdue?
- def working_days_20_overdue?
- return Holiday.due_date_from(date_response_required_by.to_date, 20) <= Time.now.to_date
+ # This is a long stop - even with UK public interest test extensions, 40
+ # days is a very long time.
+ def date_very_overdue_after
+ last_sent = last_event_forming_initial_request
+ if self.public_body.is_school?
+ # schools have 60 working days maximum (even over a long holiday)
+ return Holiday.due_date_from(self.date_initial_request_last_sent_at, 60)
+ else
+ # public interest test ICO guidance gives 40 working maximum
+ return Holiday.due_date_from(self.date_initial_request_last_sent_at, 40)
+ end
end
# Where the initial request is sent to
@@ -715,11 +738,13 @@ public
elsif status == 'waiting_response'
"Awaiting response."
elsif status == 'waiting_response_overdue'
- "Response overdue."
+ "Delayed."
+ elsif status == 'waiting_response_very_overdue'
+ "Long overdue."
elsif status == 'not_held'
"Information not held."
elsif status == 'rejected'
- "Rejected."
+ "Refused."
elsif status == 'partially_successful'
"Partially successful."
elsif status == 'successful'
@@ -894,9 +919,9 @@ public
# This is called from cron regularly.
def InfoRequest.stop_new_responses_on_old_requests
# 6 months since last change to request, only allow new incoming messages from authority domains
- InfoRequest.update_all "allow_new_responses_from = 'authority_only' where updated_at < (now() - interval '6 months') and allow_new_responses_from = 'anybody'"
+ InfoRequest.update_all "allow_new_responses_from = 'authority_only' where updated_at < (now() - interval '6 months') and allow_new_responses_from = 'anybody' and url_title <> 'holding_pen'"
# 1 year since last change requests, don't allow any new incoming messages
- InfoRequest.update_all "allow_new_responses_from = 'nobody' where updated_at < (now() - interval '1 year') and allow_new_responses_from in ('anybody', 'authority_only')"
+ InfoRequest.update_all "allow_new_responses_from = 'nobody' where updated_at < (now() - interval '1 year') and allow_new_responses_from in ('anybody', 'authority_only') and url_title <> 'holding_pen'"
end
# Returns a random FOI request
diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb
index 4e2a10a1f..1422431dc 100644
--- a/app/models/info_request_event.rb
+++ b/app/models/info_request_event.rb
@@ -282,7 +282,7 @@ class InfoRequestEvent < ActiveRecord::Base
elsif status == 'not_held'
return "Information not held"
elsif status == 'rejected'
- return "Rejection"
+ return "Refused"
elsif status == 'partially_successful'
return "Some information sent"
elsif status == 'successful'
diff --git a/app/models/outgoing_mailer.rb b/app/models/outgoing_mailer.rb
index ae7e86f4e..1546d3fd0 100644
--- a/app/models/outgoing_mailer.rb
+++ b/app/models/outgoing_mailer.rb
@@ -73,7 +73,7 @@ class OutgoingMailer < ApplicationMailer
if outgoing_message.what_doing == 'internal_review'
return "Internal review of " + info_request.email_subject_request
else
- return info_request.email_subject_followup
+ return info_request.email_subject_followup(outgoing_message.incoming_message_followup)
end
end
# Whether we have a valid email address for a followup
diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb
index 36190ce2f..40abe0b0f 100644
--- a/app/models/outgoing_message.rb
+++ b/app/models/outgoing_message.rb
@@ -39,13 +39,23 @@ class OutgoingMessage < ActiveRecord::Base
# contact address changed
has_many :info_request_events
+ # reindex if body text is edited (e.g. by admin interface)
+ after_update :xapian_reindex_after_update
+ def xapian_reindex_after_update
+ if self.changes.include?('body')
+ for info_request_event in self.info_request_events
+ info_request_event.xapian_mark_needs_index
+ end
+ end
+ end
+
# How the default letter starts and ends
def get_salutation
ret = "Dear "
if self.message_type == 'followup' && !self.incoming_message_followup.nil? && !self.incoming_message_followup.safe_mail_from.nil? && self.incoming_message_followup.valid_to_reply_to?
ret = ret + OutgoingMailer.name_for_followup(self.info_request, self.incoming_message_followup)
else
- ret = ret + "Sir or Madam"
+ ret = ret + self.info_request.public_body.name
end
return ret + ","
end
@@ -56,6 +66,9 @@ class OutgoingMessage < ActiveRecord::Base
return "Yours faithfully,"
end
end
+ def get_internal_review_insert_here_note
+ return "GIVE DETAILS ABOUT YOUR COMPLAINT HERE"
+ end
def get_default_letter
if self.what_doing == 'internal_review'
"Please pass this on to the person who conducts Freedom of Information reviews." +
@@ -64,7 +77,7 @@ class OutgoingMessage < ActiveRecord::Base
self.info_request.public_body.name +
"'s handling of my FOI request " +
"'" + self.info_request.title + "'." +
- "\n\n" +
+ "\n\n\n\n [ " + self.get_internal_review_insert_here_note + " ] \n\n\n\n" +
"A full history of my FOI request and all correspondence is available on the Internet at this address:\n" +
"http://" + MySociety::Config.get("DOMAIN", '127.0.0.1:3000') + "/request/" + self.info_request.url_title
else
@@ -119,9 +132,13 @@ class OutgoingMessage < ActiveRecord::Base
# Check have edited letter
def validate
- if self.body.empty? || self.body =~ /\A#{get_salutation}\s+#{get_signoff}/
+ if self.body.empty? || self.body =~ /\A#{get_salutation}\s+#{get_signoff}/ || self.body =~ /#{get_internal_review_insert_here_note}/
if self.message_type == 'followup'
- errors.add(:body, "^Please enter your follow up message")
+ if self.what_doing == 'internal_review'
+ errors.add(:body, "^Please give details explaining why you want a review")
+ else
+ errors.add(:body, "^Please enter your follow up message")
+ end
elsif
errors.add(:body, "^Please enter your letter requesting information")
else
diff --git a/app/models/post_redirect.rb b/app/models/post_redirect.rb
index edd151730..a17b24d16 100644
--- a/app/models/post_redirect.rb
+++ b/app/models/post_redirect.rb
@@ -61,11 +61,7 @@ class PostRedirect < ActiveRecord::Base
# Makes a random token, suitable for using in URLs e.g confirmation messages.
def self.generate_random_token
- bits = 12 * 8
- # Make range from value to double value, so number of digits in base 36
- # encoding is quite long always.
- rand_num = rand(max = 2**(bits+1)) + 2**bits
- rand_num.to_s(base=36)
+ MySociety::Util.generate_token
end
# Make the token
@@ -90,7 +86,7 @@ class PostRedirect < ActiveRecord::Base
return post_redirects[0]
end
- # Called from cron job delete-old-post-redirects
+ # Called from cron job delete-old-things
def self.delete_old_post_redirects
PostRedirect.delete_all "updated_at < (now() - interval '6 months')"
end
diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb
index 1bebb5181..7e2041b4f 100644
--- a/app/models/request_mailer.rb
+++ b/app/models/request_mailer.rb
@@ -85,7 +85,25 @@ class RequestMailer < ApplicationMailer
headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken
'Auto-Submitted' => 'auto-generated' # http://tools.ietf.org/html/rfc3834
@recipients = user.name_and_email
- @subject = "You're overdue a response to your FOI request - " + info_request.title
+ @subject = "Delayed response to your FOI request - " + info_request.title
+ @body = { :info_request => info_request, :url => url }
+ end
+
+ # Tell the requester that the public body is very late in replying
+ def very_overdue_alert(info_request, user)
+ respond_url = respond_to_last_url(info_request) + "#followup"
+
+ post_redirect = PostRedirect.new(
+ :uri => respond_url,
+ :user_id => user.id)
+ post_redirect.save!
+ url = confirm_url(:email_token => post_redirect.email_token)
+
+ @from = contact_from_name_and_email
+ headers 'Return-Path' => blackhole_email, 'Reply-To' => @from, # not much we can do if the user's email is broken
+ 'Auto-Submitted' => 'auto-generated' # http://tools.ietf.org/html/rfc3834
+ @recipients = user.name_and_email
+ @subject = "You're long overdue a response to your FOI request - " + info_request.title
@body = { :info_request => info_request, :url => url }
end
@@ -209,21 +227,35 @@ class RequestMailer < ApplicationMailer
for info_request in info_requests
alert_event_id = info_request.last_event_forming_initial_request.id
# Only overdue requests
- if info_request.calculate_status == 'waiting_response_overdue'
+ if ['waiting_response_overdue', 'waiting_response_very_overdue'].include?(info_request.calculate_status)
+ if info_request.calculate_status == 'waiting_response_overdue'
+ alert_type = 'overdue_1'
+ elsif info_request.calculate_status == 'waiting_response_very_overdue'
+ alert_type = 'very_overdue_1'
+ else
+ raise "unknown request status"
+ end
+
# For now, just to the user who created the request
- sent_already = UserInfoRequestSentAlert.find(:first, :conditions => [ "alert_type = 'overdue_1' and user_id = ? and info_request_id = ? and info_request_event_id = ?", info_request.user_id, info_request.id, alert_event_id])
+ sent_already = UserInfoRequestSentAlert.find(:first, :conditions => [ "alert_type = ? and user_id = ? and info_request_id = ? and info_request_event_id = ?", alert_type, info_request.user_id, info_request.id, alert_event_id])
if sent_already.nil?
# Alert not yet sent for this user, so send it
#STDERR.puts "sending overdue alert to info_request " + info_request.id.to_s + " user " + info_request.user_id.to_s + " event " + alert_event_id
store_sent = UserInfoRequestSentAlert.new
store_sent.info_request = info_request
store_sent.user = info_request.user
- store_sent.alert_type = 'overdue_1'
+ store_sent.alert_type = alert_type
store_sent.info_request_event_id = alert_event_id
# Only send the alert if the user can act on it by making a followup
# (otherwise they are banned, and there is no point sending it)
if info_request.user.can_make_followup?
- RequestMailer.deliver_overdue_alert(info_request, info_request.user)
+ if info_request.calculate_status == 'waiting_response_overdue'
+ RequestMailer.deliver_overdue_alert(info_request, info_request.user)
+ elsif info_request.calculate_status == 'waiting_response_very_overdue'
+ RequestMailer.deliver_very_overdue_alert(info_request, info_request.user)
+ else
+ raise "unknown request status"
+ end
end
store_sent.save!
#STDERR.puts "sent " + info_request.user.email
diff --git a/app/models/track_mailer.rb b/app/models/track_mailer.rb
index 9d8e8348d..6c9d9949b 100644
--- a/app/models/track_mailer.rb
+++ b/app/models/track_mailer.rb
@@ -33,16 +33,23 @@ class TrackMailer < ApplicationMailer
now = Time.now()
users = User.find(:all, :conditions => [ "last_daily_track_email < ?", now - 1.day ])
for user in users
- #STDERR.puts "user " + user.url_name
+ #STDERR.puts Time.now.to_s + " user " + user.url_name
email_about_things = []
track_things = TrackThing.find(:all, :conditions => [ "tracking_user_id = ? and track_medium = ?", user.id, 'email_daily' ])
for track_thing in track_things
- #STDERR.puts " track " + track_thing.track_query
+ #STDERR.puts Time.now.to_s + " track " + track_thing.track_query
# What have we alerted on already?
+ #
+ # We only use track_things_sent_emails records which are less than 14 days old.
+ # In the search query loop below, we also only use items described in last 7 days.
+ # An item described that recently definitely can't appear in track_things_sent_emails
+ # earlier, so this is safe (with a week long margin of error). If the alerts break
+ # for a whole week, then they will miss some items. Tough.
done_info_request_events = {}
- for t in track_thing.track_things_sent_emails
+ tt_sent = track_thing.track_things_sent_emails.find(:all, :conditions => ['created_at > ?', now - 14.days])
+ for t in tt_sent
if not t.info_request_event_id.nil?
done_info_request_events[t.info_request_event_id] = 1
end
@@ -51,19 +58,20 @@ class TrackMailer < ApplicationMailer
# Query for things in this track. We use described_at for the
# ordering, so we catch anything new (before described), or
# anything whose new status has been described.
- xapian_object = InfoRequest.full_search([InfoRequestEvent], track_thing.track_query, 'described_at', true, nil, 200, 1)
-
+ xapian_object = InfoRequest.full_search([InfoRequestEvent], track_thing.track_query, 'described_at', true, nil, 100, 1)
# Go through looking for unalerted things
alert_results = []
for result in xapian_object.results
- if result[:model].class.to_s == "InfoRequestEvent"
- if not done_info_request_events.include?(result[:model].id) and track_thing.created_at < result[:model].described_at
- # OK alert this one
- alert_results.push(result)
- end
- else
+ if result[:model].class.to_s != "InfoRequestEvent"
raise "need to add other types to TrackMailer.alert_tracks (unalerted)"
end
+
+ next if track_thing.created_at >= result[:model].described_at # made before the track was created
+ next if result[:model].described_at < now - 7.days # older than 1 week (see 14 days / 7 days in comment above)
+ next if done_info_request_events.include?(result[:model].id) # definitely already done
+
+ # OK alert this one
+ alert_results.push(result)
end
# If there were more alerts for this track, then store them
if alert_results.size > 0
diff --git a/app/models/track_thing.rb b/app/models/track_thing.rb
index 3392a4210..c2036118f 100644
--- a/app/models/track_thing.rb
+++ b/app/models/track_thing.rb
@@ -215,7 +215,7 @@ class TrackThing < ActiveRecord::Base
# RSS sorting - XXX hmmm, we don't really know which to use
# here for sorting. Might be a query term (e.g. 'cctv'), in
# which case newest is good, or might be something like
- # all rejected requests in which case want to sort by
+ # all refused requests in which case want to sort by
# described (when we discover criteria is met). Rather
# conservatively am picking described, as that will make
# things appear in feed more than the should, rather than less.
diff --git a/app/models/track_things_sent_email.rb b/app/models/track_things_sent_email.rb
index 35229371c..b39dad932 100644
--- a/app/models/track_things_sent_email.rb
+++ b/app/models/track_things_sent_email.rb
@@ -25,6 +25,12 @@ class TrackThingsSentEmail < ActiveRecord::Base
belongs_to :user
belongs_to :public_body
belongs_to :track_thing
+
+ # Called from cron job delete-old-things
+ def self.delete_old_track_things_sent_email
+ TrackThingsSentEmail.delete_all "updated_at < (now() - interval '1 month')"
+ end
+
end
diff --git a/app/models/user.rb b/app/models/user.rb
index bcad6229f..eb8089cf1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -134,8 +134,7 @@ class User < ActiveRecord::Base
user = self.find_user_by_email(params[:email])
if user
# There is user with email, check password
- expected_password = encrypted_password(params[:password], user.salt)
- if user.hashed_password != expected_password
+ if !user.has_this_password?(params[:password])
user.errors.add_to_base(auth_fail_message)
end
else
@@ -184,7 +183,12 @@ class User < ActiveRecord::Base
self.hashed_password = User.encrypted_password(self.password, self.salt)
end
- # For use in to/from in email messages
+ def has_this_password?(password)
+ expected_password = User.encrypted_password(password, self.salt)
+ return self.hashed_password == expected_password
+ end
+
+# For use in to/from in email messages
def name_and_email
return TMail::Address.address_from_name_and_email(self.name, self.email).to_s
end
diff --git a/app/models/user_info_request_sent_alert.rb b/app/models/user_info_request_sent_alert.rb
index 309466792..dde6fd339 100644
--- a/app/models/user_info_request_sent_alert.rb
+++ b/app/models/user_info_request_sent_alert.rb
@@ -25,6 +25,7 @@ class UserInfoRequestSentAlert < ActiveRecord::Base
validates_inclusion_of :alert_type, :in => [
'overdue_1', # tell user that info request has become overdue
+ 'very_overdue_1', # tell user that info request has become very overdue
'new_response_reminder_1', # reminder user to classify the recent response
'new_response_reminder_2', # repeat reminder user to classify the recent response
'new_response_reminder_3', # repeat reminder user to classify the recent response
diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb
index 06a115c5b..70ca42675 100644
--- a/app/models/user_mailer.rb
+++ b/app/models/user_mailer.rb
@@ -27,5 +27,26 @@ class UserMailer < ApplicationMailer
@body[:url] = url
end
+ def changeemail_confirm(user, new_email, url)
+ @from = contact_from_name_and_email
+ headers 'Return-Path' => blackhole_email, 'Reply-To' => @from # we don't care about bounces when people are fiddling with their account
+ @recipients = new_email
+ @subject = "Confirm your new email address on WhatDoTheyKnow.com"
+ @body[:name] = user.name
+ @body[:url] = url
+ @body[:old_email] = user.email
+ @body[:new_email] = new_email
+ end
+
+ def changeemail_already_used(old_email, new_email)
+ @from = contact_from_name_and_email
+ headers 'Return-Path' => blackhole_email, 'Reply-To' => @from # we don't care about bounces when people are fiddling with their account
+ @recipients = new_email
+ @subject = "Unable to change email address on WhatDoTheyKnow.com"
+ @body[:old_email] = old_email
+ @body[:new_email] = new_email
+ end
+
+
end
diff --git a/app/views/admin_public_body/import_csv.rhtml b/app/views/admin_public_body/import_csv.rhtml
index 3ca1f4d59..75981a501 100644
--- a/app/views/admin_public_body/import_csv.rhtml
+++ b/app/views/admin_public_body/import_csv.rhtml
@@ -12,15 +12,7 @@
<% form_tag 'import_csv', :multipart => true do %>
<p>
- <label for="tag">Tag to add entries to (maybe you want:
- <% for category, description in PublicBodyCategories::CATEGORIES_BY_TAG %>
- <% if category != "other" %>
- <strong><%= category %></strong>=<%= description %>;
- <% end %>
- <% end %>
- )
- </label>
- <br/>
+ <label for="tag">Tag to add entries to / alter entries for:</label>
<%= text_field_tag 'tag', params[:tag] %>
</p>
@@ -30,10 +22,23 @@
<%= file_field_tag :csv_file, :size => 60 %>
</p>
- <p><strong>Note:</strong> This will report errors in the CSV file. If there are no errors, it will make updates.
- Any changes since last import will be overwritten - e.g. email addresses changed back. Changes and additions
- are reported on a successful import for you to check.
+ <p><strong>Note:</strong> Choose <strong>dry run</strong> to test, without
+ actually altering the database. Choose <strong>upload</strong> to actually
+ make the changes. In either case, you will be shown any errors, or details
+ of the changes. When uploading, any changes since last import will be
+ overwritten - e.g. email addresses changed back.
+ </p>
- <p><%= submit_tag 'Upload' %></p>
+ <p><%= submit_tag 'Dry run' %> <%= submit_tag 'Upload' %></p>
<% end %>
+<hr>
+
+<p>Standard tags:
+ <% for category, description in PublicBodyCategories::CATEGORIES_BY_TAG %>
+ <% if category != "other" %>
+ <strong><%= category %></strong>=<%= description %>;
+ <% end %>
+ <% end %>
+ </p>
+
diff --git a/app/views/admin_request/show.rhtml b/app/views/admin_request/show.rhtml
index b9115dd59..a6256478a 100644
--- a/app/views/admin_request/show.rhtml
+++ b/app/views/admin_request/show.rhtml
@@ -6,7 +6,15 @@
<p>
<% for column in InfoRequest.content_columns %>
<strong><%= column.human_name %>:</strong> <%=h @info_request.send(column.name) %>
- <br/>
+ <% if column.name == 'described_state' %>
+ <strong>Calculated status:</strong> <%= @info_request.calculate_status %>
+ <br/><strong>Initial request last sent at:</strong> <%=@info_request.date_initial_request_last_sent_at.to_date %>
+ <strong>Date response required by:</strong> <%= @info_request.date_response_required_by %>
+ <strong>Very overdue after:</strong> <%= @info_request.date_very_overdue_after %>
+ <% end %>
+ <% if ![ 'allow_new_responses_from' ].include?(column.name) %>
+ <br/>
+ <% end %>
<% end %>
<strong>Created by:</strong> <%= user_both_links(@info_request.user) %> <br>
<strong>Public authority:</strong> <%= public_body_both_links(@info_request.public_body) %> <br>
diff --git a/app/views/admin_track/_some_tracks.rhtml b/app/views/admin_track/_some_tracks.rhtml
index 8aaae7048..72ee5fd95 100644
--- a/app/views/admin_track/_some_tracks.rhtml
+++ b/app/views/admin_track/_some_tracks.rhtml
@@ -5,7 +5,7 @@
<% for column in TrackThing.content_columns %>
<th><%= column.human_name %></th>
<% end %>
- <th>Items sent by email</th>
+ <th>Items sent by email (in last month)</th>
<th>Actions</th>
</tr>
diff --git a/app/views/admin_track/list.rhtml b/app/views/admin_track/list.rhtml
index 1d076edf5..58c87ddba 100644
--- a/app/views/admin_track/list.rhtml
+++ b/app/views/admin_track/list.rhtml
@@ -16,7 +16,7 @@
<% for column in TrackThing.content_columns %>
<th><%= column.human_name %></th>
<% end %>
- <th>Items sent by email</th>
+ <th>Items sent by email (in last month)</th>
</tr>
<% for track_thing in @admin_tracks %>
diff --git a/app/views/comment/new.rhtml b/app/views/comment/new.rhtml
index c0e7e3a22..7d7dfee6f 100644
--- a/app/views/comment/new.rhtml
+++ b/app/views/comment/new.rhtml
@@ -49,7 +49,7 @@ Annotations are so anyone, including you, can help the requester with their requ
<li> Ideas on what <strong>other documents to request</strong> which the authority may hold. </li>
<% end %>
<% if [ 'rejected' ].include?(@info_request.described_state) %>
- <li> Advise on whether the <strong>rejection is legal</strong>, and how to complain about it if not. </li>
+ <li> Advise on whether the <strong>refusal is legal</strong>, and how to complain about it if not. </li>
<% end %>
<% if [ 'internal_review' ].include?(@info_request.described_state) %>
diff --git a/app/views/general/frontpage.rhtml b/app/views/general/frontpage.rhtml
index 00b758e9f..b3fb3f12c 100644
--- a/app/views/general/frontpage.rhtml
+++ b/app/views/general/frontpage.rhtml
@@ -17,7 +17,7 @@
<br>
<br>
- OR, <strong>search</strong> for information others have requested.
+ OR, <strong>search</strong> for information others have requested using WhatDoTheyKnow.com
</p>
<% end %>
</div>
diff --git a/app/views/general/search.rhtml b/app/views/general/search.rhtml
index 1fc93b099..9993e733a 100644
--- a/app/views/general/search.rhtml
+++ b/app/views/general/search.rhtml
@@ -127,7 +127,7 @@
<table class="status_table">
<tr><td><strong><%=search_link('status:waiting_response')%></strong></td><td> Waiting for the public authority to reply </td></tr>
<tr><td><strong><%=search_link('status:not_held')%></strong></td><td> The public authority does not have the information requested </td></tr>
- <tr><td><strong><%=search_link('status:rejected')%></strong></td><td> The request was rejected by the public authority </td></tr>
+ <tr><td><strong><%=search_link('status:rejected')%></strong></td><td> The request was refused by the public authority </td></tr>
<tr><td><strong><%=search_link('status:partially_successful')%></strong></td><td> Some of the information requested has been received </td></tr>
<tr><td><strong><%=search_link('status:successful')%></strong></td><td> All of the information requested has been received </td></tr>
<tr><td><strong><%=search_link('status:waiting_clarification')%></strong></td><td> The public authority would like part of the request explained </td></tr>
diff --git a/app/views/help/about.rhtml b/app/views/help/about.rhtml
index a67c65009..5ff22b23f 100644
--- a/app/views/help/about.rhtml
+++ b/app/views/help/about.rhtml
@@ -58,6 +58,28 @@ If you like what we're doing, then you can
<h1 id="making_requests">Making requests <a href="#making_requests">#</a> </h1>
<dl>
+<dt id="which_authority">I'm not sure which authority to make my request to, how can I find out? <a href="#which_authority">#</a> </dt>
+
+<dd>
+<p>It can be hard to untangle government's complicated structured, and work out
+who knows the information that you want. Here are a few tips:
+<ul>
+<li>Browse or search WhatDoTheyKnow looking for similar requests to yours.</li>
+<li>When you've found an authority you think might have the information, use
+the "home page" link on the right hand side of their page to check what they do
+on their website.</li>
+<li>Contact the authority by phone or email to ask if they hold the kind of
+information you're after.</li>
+<li>Don't worry excessively about getting the right authority. If you get it
+wrong, they ought to advise you who to make the request to instead.
+</li>
+<li>If you've got a thorny case, please <a href="/help/contact">contact us</a> for help.</li>
+</ul>
+
+</dd>
+
+
+
<dt id="missing_body">You're missing the public authority that I want to request from! <a href="#missing_body">#</a> </dt>
<dd>
@@ -70,6 +92,29 @@ to hear from you too.
</dd>
+<dt id="authorities">Why do you include some authorities that aren't formally subject to FOI?<a href="#authorities">#</a> </dt>
+
+<dd>
+<p>WhatDoTheyKnow lets you make requests for information to a range of
+organisations:</p>
+
+<ul>
+ <li> Those formally subject to the FOI Act</li>
+ <li> Those formally subject to the Environmental Regulations (a less well
+ defined group)</li>
+ <li> Those which voluntarily comply with the FOI Act</li>
+ <li> Those which aren't subject to the Act but we think should be, on grounds
+ such as them having significant public responsibilities.
+ </li>
+</ul>
+
+<p>In the last case, we're using the site to lobby for expansion of the
+scope of the FOI Act. Even if an organisation is not legally obliged to respond
+to an FOI request, they can still do so voluntarily.
+</p>
+
+</dd>
+
<dt id="focused">Why must I keep my request focused?<a href="#focused">#</a> </dt>
<dd>
@@ -111,18 +156,20 @@ annotations after submitting the request).
<p>Making an FOI request is nearly always free.</p>
-<p>Sometimes an authority will reject your request, saying that the cost
+<p>Authorities often include unnecessary, scary, boilerplate in
+acknowledgement messages saying they "may" charge a fee. Ignore such notices.
+They hardly ever will actually charge a fee. If they do, they can only charge you if
+you have specifically agreed in advance to pay. <a
+ href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/practical_application/chargingafee.pdf">More
+ details</a> from the Information Commissioner.
+</p>
+
+<p>Sometimes an authority will refuse your request, saying that the cost
of handling it exceeds £600 (for central government) or £450 (for all other
public authorities). At this point you can refine your
request. e.g. it would be much cheaper for an authority to tell you the amount
spent on marshmallows in the past year than in the past ten years.
-.</p>
-
-<p>There are other rare cases where an authority may say that they want to charge you, such as for postage
-or photocopying. Either way, don't worry, the authority cannot make a charge unless you have
-specifically agreed in advance to pay it. <a
-href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/practical_application/chargingafee.pdf
-">More details</a> from the Information Commissioner. </p>
+</p>
</dd>
@@ -131,21 +178,18 @@ href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/prac
<dt id="quickly_response">How quickly will I get a response? <a href="#quickly_response">#</a> </dt>
<dd>
-<p>By law public authorities must respond "promptly", and in most cases not
-later than 20 working days after receiving your request. That date
-is shown on the page for your request. </p>
-
-<p>You will be emailed if this date goes by without a response, so you can send
-the public authority another note to remind them if they are breaking the
-law.</p>
+<p>By law, public authorities must respond <strong>promptly</strong> to
+requests.
+</p>
-<p>There are some cases where the hard deadline is allowed to go beyond the 20
-day period, such as if you had to clarify your request, or if they are a
-school. They will normally say if they are invoking such a reason.
-See '<a href="#days">You've calculated our deadline wrongly!</a>' for
-details about what is allowed. </p>
+<p>Even if they are not prompt, in nearly all cases they must respond within
+20 working days. If you had to clarify your request, or they are a school,
+or one or two other cases, then they may have more time
+(<a href="#days">full details</a>).
-<p>Remember though, they should anyway have responded promptly.</p>
+<p>WhatDoTheyKnow will email you if you don't get a timely response. You can
+then send the public authority a message to remind them, and tell them if they
+are breaking the law.</p>
</dd>
@@ -174,6 +218,21 @@ details about what is allowed. </p>
then read our page '<a href="/help/unhappy">Unhappy about the response you got?</a>'.
</dd>
+<dt id="reuse">It says I can't re-use the information I got!<a href="#reuse">#</a> </dt>
+<dd>
+<p>Authorities often add legal boilerplate about the
+"<a href="http://www.opsi.gov.uk/si/si2005/20051515">Re-Use of Public Sector
+Information Regulations 2005</a>", which at first glance implies you may not
+be able do anything with the information.
+</p>
+
+<p>You can, of course, write articles about the information or summarise it, or
+quote parts of it. We also think you should feel free to republish the
+information in full, just as we do, even though in theory you might not be
+allowed to do so. See <a href="#copyright">our policy on copyright</a>.</p>
+
+</dd>
+
<dt id="ico_help">Can you tell me more of the nitty gritty about the process of making requests? <a href="#ico_help">#</a> </dt>
<dd>
@@ -209,28 +268,35 @@ immediately so we can remove it.</p>
<dt id="private_requests">I'd like to keep my request secret! (At least until I publish my story) <a href="#private_requests">#</a> </dt>
-<dd>WhatDoTheyKnow is currently only designed for public requests. All
+<dd><p>WhatDoTheyKnow is currently only designed for public requests. All
responses that we receive are automatically published on the website for anyone
-to read. You should contact the public authority directly if you would like to
+to read. </p>
+<p>You should contact the public authority directly if you would like to
make a request in private. If you're interested in buying a system which helps
you manage FOI requests in secret, then <a href="/help/contact">contact us</a>.
+</p>
</dd>
<dt id="eir">Why can I only request information about the environment from some authorities? <a href="#eir">#</a> </dt>
-<dd>Some public authorities, such as <a href="http://www.whatdotheyknow.com/body/south_east_water">South East Water</a>,
+<dd>
+<p>Some public authorities, such as <a href="http://www.whatdotheyknow.com/body/south_east_water">South East Water</a>,
don't come under the Freedom of Information Act, but do come under another law called
-the Environmental Information Regulations (EIR). It's a very similar law, so you make a request
-to them using WhatDoTheyKnow in just the same way as an FOI request. The only difference
-is that on the page where you write you request, it reminds you that you can only
-request "environmental information" and tells you what that means. It is quite broad.
-</dd>
+the Environmental Information Regulations (EIR).
+</p>
-<dt id="eir_2">So can I request information using EIR from other authorities? <a href="#eir_2">#</a> </dt>
+<p>It's a very similar law, so you make a request
+to them using WhatDoTheyKnow in just the same way as an FOI request. The only
+difference is that on the page where your write you request, it reminds you
+that you can only request "environmental information" and tells you what that
+means. It is quite broad.
+</p>
-<dd>Yes, just make a Freedom of Information (FOI) request as normal. The
+<p>You can, of course, request environmental information from other
+authorities. Just make a Freedom of Information (FOI) request as normal. The
authority has a duty to work out if the Environmental Information Regulations
(EIR) is the more appropriate legislation to reply under.
+</p>
</dd>
<dt id="multiple">Can I make the same to request to lots of authorities, e.g. all councils? <a href="#multiple">#</a> </dt>
@@ -280,7 +346,8 @@ you ask us to.
<dd>
<p>We publish your request on the Internet so that anybody can read it and
-make use of the information that you have found.
+make use of the information that you have found. We do not normally delete
+requests (<a href="#delete_requests">more details</a>).
</p>
<p>
Your name is tangled up with your request, so has to be published as well.
@@ -317,7 +384,7 @@ Information Commissioner later about the handling of your request.
<ul>
<li>Use a different form of your name. The guidance says
that "Mr Arthur Thomas Roberts" can make a valid request as "Arthur Roberts",
-"A. T. Roberts", or "Mr Roberts", but not as "Arthur" or "A.T.R.".
+"A. T. Roberts", or "Mr Roberts", but <strong>not</strong> as "Arthur" or "A.T.R.".
</li>
<li>Women may use their maiden name.</li>
<li>In most cases, you may use any name by which you are "widely known and/or
@@ -326,7 +393,7 @@ is regularly used".
a company, or the trading name of a sole trader.
<li>Ask someone else to make the request on your behalf.
<li>You may, if you are really stuck, ask us to make the request on
-your behalf. Please <a href="/help/about">contact us</a> with
+your behalf. Please <a href="/help/contact">contact us</a> with
a good reason why you cannot make the request yourself and cannot
ask a friend to. We don't have the resources to do this for everyone.
</ul>
@@ -335,6 +402,28 @@ ask a friend to. We don't have the resources to do this for everyone.
</dd>
+<dt id="delete_requests">Can you delete my requests, or alter my name? <a href="#delete_requests">#</a> </dt>
+
+<dd>
+
+<p>WhatDoTheyKnow is a permanent, public archive of Freedom of
+Information requests. Even though you may not find the response to
+a request useful any more, it may be of interest to others. For this
+reason, we will not delete requests.
+</p>
+
+<p>Under exceptional circumstances we may remove or change your name
+on the website, following similar policy as for the names of
+public servants. Similarly, we may also remove other personal information. See
+'<a href="#takedown">Can you take down personal information about me?</a>'.
+</p>
+
+<p>If you're worried about this before you make your request,
+see the section on <a href="#real_name">pseudonyms</a>.</p>
+
+</dd>
+
+
<dt id="full_address">They've asked for my postal address! <a href="#full_address">#</a> </dt>
<dd>
@@ -470,7 +559,7 @@ needing any email, using the "respond to request" link at the bottom of
each request page.
</dd>
-<dt id="days">You've calculated our deadline wrongly!<a href="#days">#</a> </dt>
+<dt id="days">How do you calculate the deadline shown on request pages?<a href="#days">#</a> </dt>
<dd>
<p>The Freedom of Information Act says:</p>
@@ -493,7 +582,7 @@ to have more of that complexity visible.</p>
</dd>
-<dt id="days2">But really, you calculated it wrong!<a href="#days2">#</a> </dt>
+<dt id="days2">But really, how do you calculate the deadline?<a href="#days2">#</a> </dt>
<dd>
@@ -503,45 +592,78 @@ it is best if they show the hard work they are doing by explaining what is
taking the extra time to do.
</p>
-<p>That said, WhatDoTheyKnow does attempt to show the maximum legal deadline
-for response to each request. Here is the complex detail of how we calculate
-it.</p>
+<p>That said, WhatDoTheyKnow does show the maximum legal deadline
+for response on each request. Here's how we calculate it.</p>
<ul>
<li>If the day we deliver the request by email is a working day, we count that
-as "day zero", even if it was delivered late in the evening. Days end at midnight.
-We then count the next working day as "day one", and so on up to 20 days.</li>
+as "day zero", even if it was delivered late in the evening. Days end at
+midnight. We then count the next working day as "day one", and so on up to
+<strong>20 working days</strong>.</li>
<li>If the day the request email was delivered was a non-working day, we count
the next working day as "day one". Delivery is delivery, even if it happened on
the weekend. Some authorities <a href="http://www.whatdotheyknow.com/request/policy_regarding_body_scans#incoming-1100">disagree with this</a>, our lawyer disagrees with them. </li>
-<li>In theory, authorities can claim a time extension for applying a public
-interest test. We don't think this should be a special reason for delay. There
-are lots of other good reasons the authority might want more time, such as if
-somebody is on holiday and they can't find the information. When
-there's going to be any delay at all, we prefer it if authorities simply
-apologise and explain what they are doing that is taking the extra time, rather
-than resorting to legal minutiae.
-</li>
+<li>Requesters are encouraged to mark when they have <strong>clarified</strong>
+their request so the clock resets, but sometimes they get this wrong. If you
+see a problem with a particular request, let us know and we'll fix it.</li>
+</ul>
-<li>Since June 2009, schools have "20 working days disregarding any working
-day which is not a school day, or 60 working days, whichever is first". Basically,
-cut them some slack if it is holiday time.
-</li>
+<p>The date thus calculated is shown on requests with the text "By law,
+Liverpool City Council should normally have responded by...". There is only
+one case which is not normal, see the next question about
+<a href="#public_interest_test">public interest test time extensions</a>.
+</p>
-<li>Requesters are encouraged to mark when they have clarified their request so
-the clock resets, but sometimes they get this wrong. If you see a problem with
-a particular request, let us know and we'll fix it.</li>
+<p>Schools are also a special case, which WhatDoTheyKnow displays differently.
+</p>
+<ul>
+<li>Since June 2009, <strong>schools</strong> have "20 working days
+disregarding any working day which is not a school day, or 60 working days,
+whichever is first" (<a href="http://www.opsi.gov.uk/si/si2009/draft/ukdsi_9780111477632_en_1">FOI (Time for Compliance with Request) Regulations 2009</a>). WhatDoTheyKnow indicates on requests to schools that the 20 day deadline is only
+during term time, and shows them as definitely overdue after 60 working days
+</li>
</ul>
-<p>If you're getting really nerdy about this, read the <a href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/detailed_specialist_guides/timeforcompliance.pdf">detailed ICO guidance</a>. Meanwhile,
-remember that the law says authorities must respond <strong>promptly</strong>.
+<p>If you're getting really nerdy about all this, read the <a href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/detailed_specialist_guides/timeforcompliance.pdf">detailed ICO guidance</a>.
+Meanwhile, remember that the law says authorities must respond
+<strong>promptly</strong>. That's really what matters.</p>
</dd>
+<dt id="public_interest_test">How do you reflect time extensions for public interest tests?<a href="#public_interest_test">#</a> </dt>
+
+<dd>
+
+<p>The Freedom of Information Act lets authorities claim an indefinite time
+extension when applying a <strong>public interest test</strong>. Information
+Commissioner guidance says that it should only be used in "exceptionally
+complex" cases
+(<a href="http://www.ico.gov.uk/upload/documents/library/freedom_of_information/detailed_specialist_guides/foi_good_practice_guidance_4.pdf">FOI Good Practice Guidance No. 4</a>).
+WhatDoTheyKnow doesn't specifically handle this case, which is why we use the
+phrase "should normally have responded by" when the 20 working day time is
+exceeded.
+</p>
+
+<p>The same guidance says that, even in exceptionally complex cases, no
+Freedom of Information request should take more than <strong>40 working days</strong>
+to answer. WhatDoTheyKnow displays requests which are overdue by that much
+with stronger wording to indicate they are definitely late.
+</p>
+
+<p>The Freedom of Information (Scotland) Act does not allow such a public
+interest extension. WhatDoTheyKnow would like to see the law changed to either
+remove the extension from the UK Act, or to reintroduce an absolute time limit
+of 40 working days even with the extension (the House of Lords <a
+href="http://www.publicwhip.org.uk/division.php?date=2000-10-17&amp;number=1&amp;house=lords">voted
+to remove</a> provision for such a time limit during the initial passage
+of the UK Act through Parliament).
+</p>
+</dd>
+
<dt id="large_file">How can I send a large file, which won't go by email?<a href="#large_file">#</a> </dt>
<dd>Instead of email, you can respond to a request directly from your web
@@ -592,9 +714,14 @@ that authorities resend these with the personal information removed.</p>
<dt id="mobiles">Do you publish email addresses or mobile phone numbers? <a href="#mobiles">#</a> </dt>
-<dd>We automatically remove some emails and mobile numbers from responses to requests.
-Please <a href="/help/contact">contact us</a> if we've missed one.
-For technical reasons we don't remove them all from attachments, such as PDFs.
+<dd><p>To prevent spam, we automatically remove most emails and some mobile numbers from
+responses to requests. Please <a href="/help/contact">contact us</a> if we've
+missed one.
+For technical reasons we don't always remove them from attachments, such as certain PDFs.</p>
+<p>If you need to know what an address was that we've removed, please <a
+ href="/help/contact">get in touch with us</a>. Occasionally, an email address
+forms an important part of a response and we will post it up in an obscured
+form in an annotation.
</dd>
<dt id="copyright"><a name="commercial"></a>What is your policy on copyright of documents?<a href="#copyright">#</a> </dt>
@@ -650,8 +777,14 @@ requests, and for good public relations, we'd advise you not to do that.
</li>
<li>
The amazing team of volunteers who run the site, answer your support
- emails, maintain the database of public authorities and so much more.
- Thanks to Tony Bowden, John Cross, Adam McGreggor, Alex Skene, Richard Taylor.
+ emails, maintain the database of public authorities and
+ <a href="http://www.mysociety.org/2009/10/13/behind-whatdotheyknow/">so much more</a>.
+ Thanks to John Cross, Ben Harris, Adam McGreggor, Alex Skene,
+ Richard Taylor.
+</li>
+<li>
+ Volunteers who have provided patches to the code - thanks Peter Collingbourne
+ and Tony Bowden.
</li>
<li>
Everyone who has helped look up FOI email addresses.
diff --git a/app/views/help/unhappy.rhtml b/app/views/help/unhappy.rhtml
index cd302a81a..432c00f2e 100644
--- a/app/views/help/unhappy.rhtml
+++ b/app/views/help/unhappy.rhtml
@@ -14,7 +14,7 @@ to your request '<%=request_link(@info_request) %>'?
<ul>
<li>You didn't get a reply within 20 working days</li>
<li>You did not get all of the information that you requested <strong>or</strong></li>
-<li>Your request was rejected, but without a reason valid under the law</li>
+<li>Your request was refused, but without a reason valid under the law</li>
</ul>
<p>... you can</p>
@@ -86,7 +86,7 @@ get the information by <strong>other means...</strong></p>
<ul>
<li>Make a <strong>new FOI request</strong> for summary information, or for
-documentation relating indirectly to matters in your rejected request.
+documentation relating indirectly to matters in your refused request.
<a href="/help/contact">Ask us for ideas</a> if you're stuck.</li>
<li>If any <strong>other public authorities</strong> or publicly owned companies are involved,
then make FOI requests to them.</li>
diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml
index aad13dc97..0db75380f 100644
--- a/app/views/layouts/default.rhtml
+++ b/app/views/layouts/default.rhtml
@@ -3,14 +3,17 @@
<head>
<!-- <%= javascript_include_tag :defaults %> -->
<script type="text/javascript" src="/jslib/spell/spellChecker.js"></script>
+
<title>
<% if @title %>
<%=@title%> - WhatDoTheyKnow
<% else %>
- WhatDoTheyKnow - file and browse Freedom of Information (FOI) requests
+ WhatDoTheyKnow - make and browse Freedom of Information (FOI) requests
<% end %>
</title>
+ <link rel="shortcut icon" href="/favicon.ico">
+
<%= stylesheet_link_tag 'main', :title => "Main", :rel => "stylesheet" %>
<%= stylesheet_link_tag 'yucky-green', :title => "Yucky Green", :rel => "alternate stylesheet" %>
<!--[if LT IE 7]>
@@ -159,17 +162,14 @@
<script type="text/javascript">
var pkBaseURL = (("https:" == document.location.protocol) ? "https://piwik.mysociety.org/" : "http://piwik.mysociety.org/");
document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));
-</script>
-<script type="text/javascript">
-<!--
-piwik_action_name = '';
-piwik_idsite = 4;
-piwik_url = pkBaseURL + "piwik.php";
-piwik_log(piwik_action_name, piwik_idsite, piwik_url);
-//-->
-</script>
-<noscript><p><img src="http://piwik.mysociety.org/piwik.php?idsite=4" style="border:0" alt=""></p></noscript>
-<!-- /Piwik -->
+</script><script type="text/javascript">
+try {
+var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 4);
+piwikTracker.trackPageView();
+piwikTracker.enableLinkTracking();
+} catch( err ) {}
+</script><noscript><p><img src="http://piwik.mysociety.org/piwik.php?idsite=4" style="border:0" alt=""/></p></noscript>
+<!-- End Piwik Tag -->
<% end %>
</body>
diff --git a/app/views/public_body/list.rhtml b/app/views/public_body/list.rhtml
index 9b03e65d0..b1be9272a 100644
--- a/app/views/public_body/list.rhtml
+++ b/app/views/public_body/list.rhtml
@@ -1,32 +1,37 @@
<div id="body_sidebar">
-<h1>Show only...</h1>
-
-<h2>Alphabet</h2>
-<ul><li>
- <%= render :partial => 'alphabet' %>
-</li></ul>
-
-<% first_row = true %>
-<% for row in PublicBodyCategories::CATEGORIES_WITH_HEADINGS %>
- <% if row.instance_of?(Array) %>
- <li>
- <%= link_to_unless (@tag == row[0]), row[1], list_public_bodies_url(:tag => row[0]) %>
- </li>
- <% else %>
- <% if not first_row %>
- </ul>
+ <h1>Show only...</h1>
+
+ <h2>Alphabet</h2>
+ <ul><li>
+ <%= render :partial => 'alphabet' %>
+ </li></ul>
+
+ <% first_row = true %>
+ <% for row in PublicBodyCategories::CATEGORIES_WITH_HEADINGS %>
+ <% if row.instance_of?(Array) %>
+ <li>
+ <%= link_to_unless (@tag == row[0]), row[1], list_public_bodies_url(:tag => row[0]) %>
+ </li>
<% else %>
- <% first_row = false %>
+ <% if not first_row %>
+ </ul>
+ <% else %>
+ <% first_row = false %>
+ <% end %>
+ <h2><%=h row%></h2>
+ <ul>
<% end %>
- <h2><%=h row%></h2>
- <ul>
<% end %>
-<% end %>
-</ul>
-<p>
-<a href="/help/about#missing_body">Are we missing a public authority?</a>
-</p>
+ </ul>
+
+ <p>
+ <a href="/help/about#missing_body">Are we missing a public authority?</a>
+ </p>
+ <p>
+ <%= link_to "List of all authorities (CSV)", all_public_bodies_csv_url() %>
+ </p>
+
</div>
<% @title = "Public authorities - " + @description %>
diff --git a/app/views/public_body/show.rhtml b/app/views/public_body/show.rhtml
index f28581cdd..56d4cd75c 100644
--- a/app/views/public_body/show.rhtml
+++ b/app/views/public_body/show.rhtml
@@ -5,15 +5,19 @@
<%= render :partial => 'track/tracking_links', :locals => { :track_thing => @track_thing, :own_request => false, :location => 'sidebar' } %>
<h2>More about this authority</h2>
<% if !@public_body.calculated_home_page.nil? %>
- <%= link_to "Home page", @public_body.calculated_home_page %><br>
+ <%= link_to "Home page of authority", @public_body.calculated_home_page %><br>
<% end %>
<% if !@public_body.publication_scheme.empty? %>
<%= link_to "Publication scheme", @public_body.publication_scheme %><br>
<% end %>
<% if !@public_body.charity_number.empty? %>
- <%= link_to "Charity overview", 'http://www.charity-commission.gov.uk/SHOWCHARITY/RegisterOfCharities/CharityFramework.aspx?RegisteredCharityNumber=' + @public_body.charity_number %><br>
+ <% if @public_body.charity_number.match(/^SC/) %>
+ <%= link_to "Charity overview", "http://www.oscr.org.uk/CharityIndexDetails.aspx?id=" + @public_body.charity_number %><br>
+ <% else %>
+ <%= link_to "Charity overview", "http://www.charity-commission.gov.uk/SHOWCHARITY/RegisterOfCharities/CharityFramework.aspx?RegisteredCharityNumber=" + @public_body.charity_number %><br>
+ <% end %>
<% end %>
- <%= link_to "View FOI email address", view_public_body_email_url(@public_body.url_name) %>
+ <%= link_to "View FOI email address", view_public_body_email_url(@public_body.url_name) %><br>
</div>
<h1><%=h(@public_body.name)%></h1>
@@ -63,18 +67,18 @@
<% if !@xapian_requests.nil? %>
<% if @xapian_requests.results.empty? %>
<% if @public_body.eir_only? %>
- <h2>Environmental Information Regulations requests made</h2>
+ <h2>Environmental Information Regulations requests made using this site</h2>
<p>Nobody has made any Environmental Information Regulations requests to <%=h(@public_body.name)%> using this site yet.</p>
<% else %>
- <h2>Freedom of Information requests made</h2>
+ <h2>Freedom of Information requests made using this site</h2>
<p>Nobody has made any Freedom of Information requests to <%=h(@public_body.name)%> using this site yet.</p>
<% end %>
<% else %>
<h2>
<% if @public_body.eir_only? %>
- <%=pluralize(@public_body.info_requests.size, "Environmental Information Regulations request") %> made
+ <%=pluralize(@public_body.info_requests.size, "Environmental Information Regulations request") %> made using this site
<% else %>
- <%=pluralize(@public_body.info_requests.size, "Freedom of Information request") %> made
+ <%=pluralize(@public_body.info_requests.size, "Freedom of Information request") %> made using this site
<% end %>
<%= @page_desc %>
</h2>
diff --git a/app/views/request/_after_actions.rhtml b/app/views/request/_after_actions.rhtml
index d51c6bfb3..9bef04ce4 100644
--- a/app/views/request/_after_actions.rhtml
+++ b/app/views/request/_after_actions.rhtml
@@ -24,7 +24,7 @@
<% if @last_response.nil? %>
<%= link_to "Send follow up to " + OutgoingMailer.name_for_followup(@info_request, @last_response), show_response_no_followup_url(:id => @info_request.id, :incoming_message_id => nil) + "#followup" %>
<% else %>
- <% cache(:controller => "request", :action => "show_response", :id => @info_request.id, :incoming_message_id => @last_response.id, :only_path => true, :template => "_after_actions", :section => "reply_to_link") do %>
+ <% foi_cache(:controller => "request", :action => "show_response", :id => @info_request.id, :incoming_message_id => @last_response.id, :only_path => true, :template => "_after_actions", :section => "reply_to_link") do %>
<%= link_to "Reply to " + OutgoingMailer.name_for_followup(@info_request, @last_response), show_response_url(:id => @info_request.id, :incoming_message_id => @last_response.id) + "#followup" %>
<% end %>
<% end %>
diff --git a/app/views/request/_correspondence.rhtml b/app/views/request/_correspondence.rhtml
index 90beb4050..0756b0797 100644
--- a/app/views/request/_correspondence.rhtml
+++ b/app/views/request/_correspondence.rhtml
@@ -6,7 +6,7 @@ end
if not incoming_message.nil?
%>
<div class="correspondence" id="incoming-<%=incoming_message.id.to_s%>">
- <% cache(:controller => "request", :action => "show_response", :id => @info_request.id, :incoming_message_id => incoming_message.id, :only_path => true, :template => "_correspondence", :section => "incoming_message_bubble", :collapse => @collapse_quotes ? nil : 'no' ) do %>
+ <% foi_cache(:controller => "request", :action => "show_response", :id => @info_request.id, :incoming_message_id => incoming_message.id, :only_path => true, :template => "_correspondence", :section => "incoming_message_bubble", :collapse => @collapse_quotes ? nil : 'no' ) do %>
<h2>
<% if !incoming_message.safe_mail_from.nil? && incoming_message.safe_mail_from.strip != @info_request.public_body.name.strip %>
<%=h incoming_message.safe_mail_from %><br>
diff --git a/app/views/request/_describe_state.rhtml b/app/views/request/_describe_state.rhtml
index 4496fddb6..a91231ae8 100644
--- a/app/views/request/_describe_state.rhtml
+++ b/app/views/request/_describe_state.rhtml
@@ -58,7 +58,7 @@
</div>
<div>
<%= radio_button "incoming_message", "described_state", "rejected", :id => 'rejected' + id_suffix %>
- <label for="rejected<%=id_suffix%>">My request has been <strong>rejected</strong></label>
+ <label for="rejected<%=id_suffix%>">My request has been <strong>refused</strong></label>
</div>
<hr> <!------------------------------------------------>
diff --git a/app/views/request/_followup.rhtml b/app/views/request/_followup.rhtml
index 2429c63b2..b375befd1 100644
--- a/app/views/request/_followup.rhtml
+++ b/app/views/request/_followup.rhtml
@@ -19,46 +19,42 @@
<a href="/help/contact">contact us</a> if you are <%= user_link(@info_request.user) %>
and need to send a follow up.</p>
<% else %>
-
-<!-- <p>Please do <strong>not</strong> make new requests here.
- If you are asking for information which was not in your original request, then
- <%= link_to "file a new request", new_request_to_body_url(:public_body_id => @info_request.public_body.id.to_s) %>
- instead.
- </p>
- -->
-
<% if @internal_review %>
<p>
If you are dissatisfied by the response you got from
the public authority, you have the right to
complain (<a href="http://foiwiki.com/foiwiki/index.php/Internal_reviews">details</a>).
</p>
-
<% end %>
- <% if @info_request.calculate_status == 'waiting_response_overdue' %>
- <p>
- <% if @info_request.working_days_20_overdue? %>
- This request is <strong>long overdue a response</strong>.
- <% else %>
- This request is <strong>overdue a response</strong>.
- <% end %>
- You can say that, by law, the authority should have answered
- <strong>promptly</strong>. If they have not given you a legal
- reason why they need extra time
- (<%= link_to "more details", about_url + "#quickly_response" %>), then
- you can say they are breaking the law to have not replied by
- <strong><%= simple_date(@info_request.date_response_required_by) %></strong>.
+ <p>Please <strong>only</strong> write messages directly relating to your
+ request '<%= request_link(@info_request) %>'. If you would like to ask for information
+ that was not in your original request, then
+ <%= link_to "file a new request", new_request_to_body_url(:public_body_id => @info_request.public_body.id.to_s) %>.
+ </p>
+
+ <% status = @info_request.calculate_status %>
+ <% if status == 'waiting_response_overdue' %>
+ <p>The response to your request has been <strong>delayed</strong>. You can say that,
+ by law, the authority should normally have responded
+ <strong>promptly</strong> and
<% if @info_request.public_body.is_school? %>
- This is a school, so legally they get lots of extra slack if it is
- holiday time.
+ in term time
<% end %>
- </p>
+ by <strong><%= simple_date(@info_request.date_response_required_by) %></strong>
+ (<%= link_to "details", about_url + "#quickly_response" %>).
+ </p>
+ <% elsif status == 'waiting_response_very_overdue' %>
+ <p>
+ The response to your request is <strong>long overdue</strong>. You can say that, by
+ law, under all circumstances, the authority should have responded
+ by now (<%= link_to "details", about_url + "#quickly_response" %>).
+ </p>
<% end %>
<% form_for(:outgoing_message, @outgoing_message, :html => { :id => 'followup_form' }, :url => incoming_message.nil? ? show_response_no_followup_url(:id => @info_request.id) : show_response_url(:id => @info_request.id, :incoming_message_id => incoming_message.id)) do |o| %>
<p>
- <%= o.text_area :body, :rows => 10, :cols => 55 %>
+ <%= o.text_area :body, :rows => 15, :cols => 55 %>
<br><script type="text/javascript">document.write('<input name="doSpell" type="button" value="Check spelling" onClick="openSpellChecker(document.getElementById(\'followup_form\').body);"/> (optional)')</script>
</p>
@@ -93,12 +89,7 @@
<% if @internal_review %>
<p>Edit and add <strong>more details</strong> to the message above,
- explaining why you would like a review.
- <ul>
- <li>Say that you are dissatisfied by their response</li>
- <li>Set out your reasons why</li>
- <li>Ask them to review their response</li>
- </ul>
+ explaining why you are dissatisfied with their response.
</p>
<% end %>
diff --git a/app/views/request/_hidden_correspondence.rhtml b/app/views/request/_hidden_correspondence.rhtml
new file mode 100644
index 000000000..2c168c3fb
--- /dev/null
+++ b/app/views/request/_hidden_correspondence.rhtml
@@ -0,0 +1,37 @@
+<% if info_request_event.prominence == 'requester_only' %>
+ <%
+ if !info_request_event.nil? && info_request_event.event_type == 'response'
+ incoming_message = info_request_event.incoming_message
+ end
+ if not incoming_message.nil?
+ %>
+ <div class="correspondence" id="incoming-<%=incoming_message.id.to_s%>">
+ <p>This response has been hidden. See annotations to find out why.
+ If you are the requester, then you may
+ <%= link_to "sign in", signin_url(:r => request.request_uri) %>
+ to view the response.
+ </p>
+ </div>
+ <% elsif [ 'sent', 'followup_sent', 'resent', 'followup_resent' ].include?(info_request_event.event_type) %>
+ <div class="correspondence" id="outgoing-<%=outgoing_message.id.to_s%>">
+ <p>This outgoing message has been hidden. See annotations to
+ find out why. If you are the requester, then you may <%= link_to
+ "sign in", signin_url(:r => request.request_uri) %> to view the
+ response.
+ </p>
+ </div>
+ <% elsif info_request_event.event_type == 'comment' %>
+ <div class="comment_in_request" id="comment-<%=comment.id.to_s%>">
+ <p>This comment has been hidden. See annotations to
+ find out why. If you are the requester, then you may <%= link_to
+ "sign in", signin_url(:r => request.request_uri) %> to view the
+ response.
+ </p>
+ </div>
+ <% end %>
+
+<% elsif info_request_event.prominence == 'hidden' %>
+ <% # show nothing when hidden %>
+<% else %>
+ <% raise "unexpected prominence on request event" %>
+<% end %>
diff --git a/app/views/request/_other_describe_state.rhtml b/app/views/request/_other_describe_state.rhtml
index 866e90142..66f64c27a 100644
--- a/app/views/request/_other_describe_state.rhtml
+++ b/app/views/request/_other_describe_state.rhtml
@@ -54,7 +54,7 @@
</div>
<div>
<%= radio_button "incoming_message", "described_state", "rejected", :id => 'rejected' + id_suffix %>
- <label for="rejected<%=id_suffix%>">The request has been <strong>rejected</strong></label>
+ <label for="rejected<%=id_suffix%>">The request has been <strong>refused</strong></label>
</div>
<hr> <!------------------------------------------------>
diff --git a/app/views/request/_sidebar.rhtml b/app/views/request/_sidebar.rhtml
index 0cb9207d8..1509bf494 100644
--- a/app/views/request/_sidebar.rhtml
+++ b/app/views/request/_sidebar.rhtml
@@ -31,6 +31,7 @@
<!-- Important terms: <%= @xapian_similar.important_terms.join(" ") %> -->
<% end %>
+ <p><%= link_to "Event history details", request_details_url(@info_request) %></p>
<p><a href="/help/about#commercial">Are you the owner of
any commercial copyright on this page?</a></p>
-</div> \ No newline at end of file
+</div>
diff --git a/app/views/request/details.rhtml b/app/views/request/details.rhtml
new file mode 100644
index 000000000..db7e652f6
--- /dev/null
+++ b/app/views/request/details.rhtml
@@ -0,0 +1,54 @@
+<% @title = "Details of request '" + h(@info_request.title) + "'" %>
+<h1><%="Details of request '" + request_link(@info_request) + "'" %></h1>
+
+<h2>Event history</h2>
+
+<p>This table shows the technical details of the internal events that happened
+to this request on WhatDoTheyKnow. This could be used to generate information about
+the speed with which authorities respond to requests, the number of requests
+which require a postal response and much more.
+</p>
+
+<p><strong>Caveat emptor!</strong> To use this data in an honourable way, you will need
+a good internal knowledge of user behaviour on WhatDoTheyKnow. How,
+why and by whom requests are categorised is not straightforward, and there will
+be user error and ambiguity. You will also need to understand FOI law, and the
+way authorities use it. Plus you'll need to be an elite statistician. Please
+<a href="/help/contact">contact us</a> with questions.
+</p>
+
+<% columns = ['id', 'event_type', 'created_at', 'described_state', 'calculated_state', 'last_described_at' ] %>
+
+<table>
+ <tr>
+ <% for column in @columns%>
+ <th><%= column %></th>
+ <% end %>
+ <th>link</th>
+ </tr>
+
+<% for info_request_event in @info_request.info_request_events.find(:all, :order => "created_at, id") %>
+ <tr class="<%= cycle('odd', 'even') %>">
+ <% for column in @columns %>
+ <td>
+ <%=h info_request_event.send(column) %>
+ </td>
+ <% end %>
+ <td>
+ <% if info_request_event.outgoing_message %>
+ <%= link_to "outgoing", outgoing_message_url(info_request_event.outgoing_message) %>
+ <% end %>
+ <% if info_request_event.incoming_message %>
+ <%= link_to "incoming", incoming_message_url(info_request_event.incoming_message) %>
+ <% end %>
+ </td>
+ </tr>
+<% end %>
+</table>
+
+<p>Here <strong>described</strong> means when a user selected a status for the request, and
+the most recent event had its status updated to that value. <strong>calculated</strong> is then inferred by
+WhatDoTheyKnow for intermediate events, which weren't given an explicit
+description by a user. See the <a href="/search">search tips</a> for description of the states.</p>
+
+
diff --git a/app/views/request/new.rhtml b/app/views/request/new.rhtml
index aa9bf254d..b48966e2f 100644
--- a/app/views/request/new.rhtml
+++ b/app/views/request/new.rhtml
@@ -124,7 +124,7 @@
<p class="form_note">
Everything that you enter on this page, including <strong>your name</strong>,
will be <strong>displayed publicly</strong> on
- this website (<a href="/help/about/#public_request">why?</a>).
+ this website forever (<a href="/help/about/#public_request">why?</a>).
If you are thinking of using a pseudonym,
please <a href="/help/about/#real_name">read this first</a>.
</p>
@@ -132,7 +132,7 @@
<p class="form_note">
Everything that you enter on this page
will be <strong>displayed publicly</strong> on
- this website (<a href="/help/about/#public_request">why?</a>).
+ this website forever (<a href="/help/about/#public_request">why?</a>).
</p>
<% end %>
diff --git a/app/views/request/show.rhtml b/app/views/request/show.rhtml
index 5b89a6d12..97dc32512 100644
--- a/app/views/request/show.rhtml
+++ b/app/views/request/show.rhtml
@@ -56,34 +56,34 @@
<% end %>
<% elsif @status == 'waiting_response' %>
Currently <strong>waiting for a response</strong> from <%= public_body_link(@info_request.public_body) %>,
- they <%= link_to "must respond", about_url + "#quickly_response" %>
- promptly but no later than <strong><%= simple_date(@info_request.date_response_required_by) %></strong>.
- <% elsif @status == 'waiting_response_overdue' %>
- <% if @info_request.working_days_20_overdue? %>
- This request is <strong>long overdue a response</strong>.
- By law, <%= public_body_link(@info_request.public_body) %>
- should normally have answered by
- <strong><%= simple_date(@info_request.date_response_required_by) %></strong>
- (<%= link_to "more details", about_url + "#quickly_response" %>).
- You can <strong>complain</strong> by
- <%= link_to "requesting an internal review", show_response_no_followup_url(:id => @info_request.id, :incoming_message_id => nil) + "?internal_review=1#followup" %>.
- <% else %>
- This request is <strong>overdue a response</strong>.
- By law, <%= public_body_link(@info_request.public_body) %>
- should normally have answered by
- <strong><%= simple_date(@info_request.date_response_required_by) %></strong>.
- If they need extra time they should have told you
- why (<%= link_to "more details", about_url + "#quickly_response" %>).
- <% end %>
+ they must respond promptly and
<% if @info_request.public_body.is_school? %>
- This is a school, so legally they get lots of extra slack if it is
- holiday time.
+ in term time
+ <% else %>
+ normally
<% end %>
-
+ no later than <strong><%= simple_date(@info_request.date_response_required_by) %></strong>
+ (<%= link_to "details", about_url + "#quickly_response" %>).
+ <% elsif @status == 'waiting_response_overdue' %>
+ Response to this request is <strong>delayed</strong>.
+ By law, <%= public_body_link(@info_request.public_body) %> should
+ normally have responded <strong>promptly</strong> and
+ <% if @info_request.public_body.is_school? %>
+ in term time
+ <% end %>
+ by <strong><%= simple_date(@info_request.date_response_required_by) %></strong>
+ (<%= link_to "details", about_url + "#quickly_response" %>).
+ <% elsif @status == 'waiting_response_very_overdue' %>
+ Response to this request is <strong>long overdue</strong>.
+ By law, under all circumstances, <%= public_body_link(@info_request.public_body) %>
+ should have responded by now
+ (<%= link_to "details", about_url + "#quickly_response" %>).
+ You can <strong>complain</strong> by
+ <%= link_to "requesting an internal review", show_response_no_followup_url(:id => @info_request.id, :incoming_message_id => nil) + "?internal_review=1#followup" %>.
<% elsif @status == 'not_held' %>
<%= public_body_link(@info_request.public_body) %> <strong>did not have</strong> the information requested.
<% elsif @status == 'rejected' %>
- The request was <strong>rejected</strong> by <%= public_body_link(@info_request.public_body) %>.
+ The request was <strong>refused</strong> by <%= public_body_link(@info_request.public_body) %>.
<% elsif @status == 'successful' %>
The request was <strong>successful</strong>.
<% elsif @status == 'partially_successful' %>
diff --git a/app/views/request/show_response.rhtml b/app/views/request/show_response.rhtml
index 1d841c3a8..ed32a1b67 100644
--- a/app/views/request/show_response.rhtml
+++ b/app/views/request/show_response.rhtml
@@ -10,7 +10,7 @@
<% if @gone_postal %>
<div class="gone_postal_help">
- <h1>What exactly is happening?</h1>
+ <h1>Which of these is happening?</h1>
<dl>
diff --git a/app/views/request_mailer/overdue_alert.rhtml b/app/views/request_mailer/overdue_alert.rhtml
index ab2faf212..29a1a1d68 100644
--- a/app/views/request_mailer/overdue_alert.rhtml
+++ b/app/views/request_mailer/overdue_alert.rhtml
@@ -1,10 +1,10 @@
-<%= @info_request.public_body.name %> are late.
+<%= @info_request.public_body.name %> have delayed.
They have not replied to your <%=@info_request.law_used_short%> request '<%= @info_request.title %>'
-promptly, as required by law.
+promptly, as normally required by law<% if @info_request.public_body.is_school? %> during term time<% end %>.
Click on the link below to send a message to <%= @info_request.public_body.name
-%> reminding them to reply to your request. <% if @info_request.public_body.is_school? %> This is a school, so legally they get lots of extra slack if it is holiday time. <% end %>
+%> reminding them to reply to your request.
<%=@url%>
diff --git a/app/views/request_mailer/very_overdue_alert.rhtml b/app/views/request_mailer/very_overdue_alert.rhtml
new file mode 100644
index 000000000..2393d29e5
--- /dev/null
+++ b/app/views/request_mailer/very_overdue_alert.rhtml
@@ -0,0 +1,14 @@
+<%= @info_request.public_body.name %> are long overdue.
+
+They have not replied to your <%=@info_request.law_used_short%> request '<%= @info_request.title %>',
+as required by law<% if @info_request.public_body.is_school? %> even during holidays<% end %>.
+
+Click on the link below to send a message to <%= @info_request.public_body.name
+%> telling them to reply to your request. You might like to ask for an internal
+review, asking them to find out why response to the request has been so slow.
+
+<%=@url%>
+
+-- the WhatDoTheyKnow team
+
+
diff --git a/app/views/user/_signin.rhtml b/app/views/user/_signin.rhtml
index 812bf9b4b..52c2a9e71 100644
--- a/app/views/user/_signin.rhtml
+++ b/app/views/user/_signin.rhtml
@@ -18,7 +18,7 @@
</p>
<p class="form_note">
- <%= link_to "Forgotten your password?", signchange_url + "?pretoken=" + h(params[:token]) %>
+ <%= link_to "Forgotten your password?", signchangepassword_url + "?pretoken=" + h(params[:token]) %>
</p>
<p class="form_checkbox">
diff --git a/app/views/user/show.rhtml b/app/views/user/show.rhtml
index 59a9cfcd4..4d2020cdc 100644
--- a/app/views/user/show.rhtml
+++ b/app/views/user/show.rhtml
@@ -50,7 +50,8 @@
<%= link_to "Send message to " + h(@display_user.name), contact_user_url(:id => @display_user.id) %>
<% if @is_you %>
(just to see how it works)
- <br><%= link_to "Change your password", signchange_url() %>
+ <br><%= link_to "Change your password", signchangepassword_url() %> |
+ <br><%= link_to "Change your email", signchangeemail_url() %> |
<br><%= link_to "Set profile photo", profile_photo_url() %>
<% end %>
</p>
diff --git a/app/views/user/signchange_send_confirm.rhtml b/app/views/user/signchange_send_confirm.rhtml
deleted file mode 100644
index e1462760c..000000000
--- a/app/views/user/signchange_send_confirm.rhtml
+++ /dev/null
@@ -1,30 +0,0 @@
-<% @title = "Change password" %>
-
-<div id="change_password">
-
-<% form_tag({:action => "signchange"}, {:id => "signchange_form"}) do %>
- <%= foi_error_messages_for :signchange %>
-
- <div class="form_note">
- <h1>Change your password</h1>
- </div>
-
- <p>
- <label class="form_label" for="signchange_email">Your e-mail:</label>
- <%= text_field 'signchange', 'email', { :size => 20 } %>
- </p>
-
- <p class="form_note">
- <strong>Note:</strong>
- We will send you an email. Follow the instructions in it to change
- your password.
- </p>
-
- <div class="form_button">
- <%= hidden_field_tag 'submitted_signchange_send_confirm', 1 %>
- <%= hidden_field_tag 'pretoken', params[:pretoken] %>
- <%= submit_tag "Submit" %>
- </div>
-<% end %>
-
-</div>
diff --git a/app/views/user/signchangeemail.rhtml b/app/views/user/signchangeemail.rhtml
new file mode 100644
index 000000000..b98dc383c
--- /dev/null
+++ b/app/views/user/signchangeemail.rhtml
@@ -0,0 +1,41 @@
+<% @title = "Change your email address used on WhatDoTheyKnow.com" %>
+
+<% raise "internal error" if not @user %>
+
+<div id="change_email">
+
+<% form_tag({:action => "signchangeemail"}, {:id => "signchangeemail_form"}) do %>
+ <%= foi_error_messages_for :signchangeemail %>
+
+ <div class="form_note">
+ <h1>Change your email address used on WhatDoTheyKnow.com</h1>
+ </div>
+
+ <p>
+ <label class="form_label" for="signchangeemail_old_email">Old e-mail:</label>
+ <%= text_field 'signchangeemail', 'old_email', { :size => 20 } %>
+ </p>
+
+ <p>
+ <label class="form_label" for="signchangeemail_new_email">New e-mail:</label>
+ <%= text_field 'signchangeemail', 'new_email', { :size => 20 } %>
+ </p>
+
+ <p>
+ <label class="form_label" for="signchangeemail_password">Your password:</label>
+ <%= password_field 'signchangeemail', 'password', { :size => 15 } %>
+ </p>
+
+ <p class="form_note">
+ <strong>Note:</strong>
+ We will send an email to your new email address. Follow the
+ instructions in it to confirm changing your email.
+ </p>
+
+ <div class="form_button">
+ <%= hidden_field_tag 'submitted_signchangeemail_do', 1 %>
+ <%= submit_tag "Change email on WhatDoTheyKnow.com" %>
+ </div>
+<% end %>
+
+</div>
diff --git a/app/views/user/signchangeemail_confirm.rhtml b/app/views/user/signchangeemail_confirm.rhtml
new file mode 100644
index 000000000..96acbf424
--- /dev/null
+++ b/app/views/user/signchangeemail_confirm.rhtml
@@ -0,0 +1,14 @@
+<% @title = h("Now check your email!") %>
+
+<h1 class="confirmation_heading">Now check your email!</h1>
+
+<p class="confirmation_message">
+We've sent an email to your new email address. You'll need to click the link in
+it before your email address will be changed.
+</p>
+
+<p class="confirmation_message">
+<small>If you use web-based email or have "junk mail" filters, also check your
+bulk/spam mail folders. Sometimes, our messages are marked that way.</small>
+</p>
+
diff --git a/app/views/user/signchange.rhtml b/app/views/user/signchangepassword.rhtml
index 032d80945..4191344cb 100644
--- a/app/views/user/signchange.rhtml
+++ b/app/views/user/signchangepassword.rhtml
@@ -1,14 +1,14 @@
-<% @title = "Change password" %>
+<% @title = "Change your password on WhatDoTheyKnow.com" %>
<% raise "internal error" if not @user %>
<div id="change_password">
-<% form_tag({:action => "signchange"}, {:id => "signchange_form"}) do %>
+<% form_tag({:action => "signchangepassword"}, {:id => "signchangepassword_form"}) do %>
<%= foi_error_messages_for :user %>
<div class="form_note">
- <h1>Change your password</h1>
+ <h1>Change your password on WhatDoTheyKnow.com</h1>
</div>
<p>
@@ -22,9 +22,9 @@
</p>
<div class="form_button">
- <%= hidden_field_tag 'submitted_signchange_password', 1 %>
+ <%= hidden_field_tag 'submitted_signchangepassword_do', 1 %>
<%= hidden_field_tag 'pretoken', params[:pretoken] %>
- <%= submit_tag "Change password" %>
+ <%= submit_tag "Change password on WhatDoTheyKnow.com" %>
</div>
<% end %>
diff --git a/app/views/user/signchange_confirm.rhtml b/app/views/user/signchangepassword_confirm.rhtml
index baad6729b..baad6729b 100644
--- a/app/views/user/signchange_confirm.rhtml
+++ b/app/views/user/signchangepassword_confirm.rhtml
diff --git a/app/views/user/signchangepassword_send_confirm.rhtml b/app/views/user/signchangepassword_send_confirm.rhtml
new file mode 100644
index 000000000..8b2e4fa91
--- /dev/null
+++ b/app/views/user/signchangepassword_send_confirm.rhtml
@@ -0,0 +1,30 @@
+<% @title = "Change your password on WhatDoTheyKnow.com" %>
+
+<div id="change_password">
+
+<% form_tag({:action => "signchangepassword"}, {:id => "signchangepassword_form"}) do %>
+ <%= foi_error_messages_for :signchangepassword %>
+
+ <div class="form_note">
+ <h1>Change your password on WhatDoTheyKnow.com</h1>
+ </div>
+
+ <p>
+ <label class="form_label" for="signchangepassword_email">Your e-mail:</label>
+ <%= text_field 'signchangepassword', 'email', { :size => 20 } %>
+ </p>
+
+ <p class="form_note">
+ <strong>Note:</strong>
+ We will send you an email. Follow the instructions in it to change
+ your password.
+ </p>
+
+ <div class="form_button">
+ <%= hidden_field_tag 'submitted_signchangepassword_send_confirm', 1 %>
+ <%= hidden_field_tag 'pretoken', params[:pretoken] %>
+ <%= submit_tag "Submit" %>
+ </div>
+<% end %>
+
+</div>
diff --git a/app/views/user_mailer/changeemail_already_used.rhtml b/app/views/user_mailer/changeemail_already_used.rhtml
new file mode 100644
index 000000000..0f60ad798
--- /dev/null
+++ b/app/views/user_mailer/changeemail_already_used.rhtml
@@ -0,0 +1,9 @@
+Someone, perhaps you, just tried to change their email address on
+WhatDoTheyKnow.com from <%=@old_email%> to <%=@new_email%>.
+
+This was not possible because there is already an account using
+the email address <%=@new_email%>.
+
+The accounts have been left as they previously were.
+
+-- the WhatDoTheyKnow team
diff --git a/app/views/user_mailer/changeemail_confirm.rhtml b/app/views/user_mailer/changeemail_confirm.rhtml
new file mode 100644
index 000000000..9aa288fb0
--- /dev/null
+++ b/app/views/user_mailer/changeemail_confirm.rhtml
@@ -0,0 +1,12 @@
+<%= @name %>,
+
+Please click on the link below to confirm that you want to
+change the email address that you use for WhatDoTheyKnow
+from <%=@old_email%> to <%=@new_email%>
+
+<%=@url%>
+
+We will not reveal your email addresses to anybody unless you
+or the law tell us to.
+
+-- the WhatDoTheyKnow team
diff --git a/commonlib b/commonlib
-Subproject a901c2a431f7869f5c2eaee5808f8590ca78544
+Subproject 5baec579b8ede57d71e114a8721d2d4a2c667ef
diff --git a/config/crontab.ugly b/config/crontab.ugly
index 004f1cb7a..ee2083c65 100644
--- a/config/crontab.ugly
+++ b/config/crontab.ugly
@@ -22,7 +22,7 @@ MAILTO=team@whatdotheyknow.com
31 * * * * root run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/load-exim-logs.lock /data/vhost/!!(*= $vhost *)!!/whatdotheyknow/script/load-exim-logs || echo "stalled?"
# Once a day, early morning
-23 4 * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/delete-old-post-redirects.lock /data/vhost/!!(*= $vhost *)!!/whatdotheyknow/script/delete-old-post-redirects || echo "stalled?"
+23 4 * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/delete-old-things.lock /data/vhost/!!(*= $vhost *)!!/whatdotheyknow/script/delete-old-things || echo "stalled?"
0 8 * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/alert-new-response-reminders.lock /data/vhost/!!(*= $vhost *)!!/whatdotheyknow/script/alert-new-response-reminders || echo "stalled?"
0 8 * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/alert-not-clarified-request.lock /data/vhost/!!(*= $vhost *)!!/whatdotheyknow/script/alert-not-clarified-request || echo "stalled?"
2 4 * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/check-recent-requests-sent.lock /data/vhost/!!(*= $vhost *)!!/whatdotheyknow/script/check-recent-requests-sent || echo "stalled?"
diff --git a/config/environment.rb b/config/environment.rb
index 6d0c2f5c0..b0effaabd 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -6,19 +6,25 @@
# ENV['RAILS_ENV'] ||= 'production'
# Specifies gem version of Rails to use when vendor/rails is not present
-RAILS_GEM_VERSION = '2.1.0' unless defined? RAILS_GEM_VERSION
+RAILS_GEM_VERSION = '2.3.2' unless defined? RAILS_GEM_VERSION
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
# MySociety specific helper functions
$:.push(File.join(File.dirname(__FILE__), '../commonlib/rblib'))
-# ... if these fail to include, you need the rblib directory from
-# mySociety CVS, put it at the same level as the foi directory.
+# ... if these fail to include, you need the commonlib submodule from git
+# (type "git submodule update --init" in the whatdotheyknow directory)
+
+# ruby-ole and ruby-msg. We use a custom ruby-msg to avoid a name conflict
+$:.unshift(File.join(File.dirname(__FILE__), '../vendor/ruby-ole/lib'))
+$:.unshift(File.join(File.dirname(__FILE__), '../vendor/ruby-msg/lib'))
+
load "validate.rb"
load "config.rb"
load "format.rb"
load "debug_helpers.rb"
+load "util.rb"
Rails::Initializer.run do |config|
# Load intial mySociety config
@@ -45,8 +51,8 @@ Rails::Initializer.run do |config|
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
config.action_controller.session = {
- :session_key => '_foi_cookie_session',
- :secret => MySociety::Config.get("COOKIE_STORE_SESSION_SECRET", 'this default is insecure as code is open source, please override for live sites in config/general; this will do for local development'),
+ :session_key => '_wdtk_cookie_session',
+ :secret => MySociety::Config.get("COOKIE_STORE_SESSION_SECRET", 'this default is insecure as code is open source, please override for live sites in config/general; this will do for local development')
}
config.action_controller.session_store = :cookie_store
@@ -101,6 +107,7 @@ require 'make_html_4_compliant.rb'
require 'activerecord_errors_extensions.rb'
require 'willpaginate_hack.rb'
require 'sendmail_return_path.rb'
+require 'tnef.rb'
# XXX temp debug for SQL logging production sites
#ActiveRecord::Base.logger = Logger.new(STDOUT)
diff --git a/config/general-example b/config/general-example
index 70345400d..bc8e03f65 100644
--- a/config/general-example
+++ b/config/general-example
@@ -43,6 +43,11 @@ define('OPTION_ADMIN_PUBLIC_URL', '/'); // where /stylesheets sits under for adm
// Secret key for signing cookie_store sessions
define('OPTION_COOKIE_STORE_SESSION_SECRET', 'your secret key here, make it long and random');
+// If present, puts the site in read only mode, and uses the text as reason
+// (whole paragraph). Please use a read-only database user as well, as it only
+// checks in a few obvious places.
+define('OPTION_READ_ONLY', '');
+
// Recaptcha, for detecting humans. Get keys here: http://recaptcha.net/whyrecaptcha.html
define('OPTION_RECAPTCHA_PUBLIC_KEY', 'x');
define('OPTION_RECAPTCHA_PRIVATE_KEY', 'x');
diff --git a/config/packages b/config/packages
index f0bed1763..5a45a1446 100644
--- a/config/packages
+++ b/config/packages
@@ -1,16 +1,18 @@
ruby1.8
libpgsql-ruby1.8
libopenssl-ruby1.8
-rake
+rake (>= 0.8.4-1)
irb
wv
poppler-utils
-# poppler-utils (>= 0.12.0)
-pdftk
+# poppler-utils (>= 0.12.0) # this is much better when it is available in Debian stable
+pdftk (> 1.41+dfsg-1) | pdftk (< 1.41+dfsg-1) # that version has a non-functionining uncompress option
gs-gpl
catdoc
links
-lynx
+elinks
+unrtf
+xlhtml
libxapian-ruby1.8
gnuplot-nox
ttf-bitstream-vera
@@ -20,6 +22,11 @@ libzip-ruby1.8
libzlib-ruby
mahoro-ruby1.8 | libmahoro-ruby1.8
wdg-html-validator
-#libapache2-mod-passenger
+# libapache2-mod-passenger
mutt
-librack-ruby1.8
+librack-ruby1.8 (>= 1.0.1-1)
+librmagick-ruby1.8
+libxml-simple-ruby
+libfcgi-ruby1.8
+vpim
+tnef (>= 1.4.5)
diff --git a/config/routes.rb b/config/routes.rb
index a37227fc0..65858f9df 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -37,6 +37,7 @@ ActionController::Routing::Routes.draw do |map|
request.new_request_to_body '/new/:public_body_id', :action => 'new'
request.show_request '/request/:url_title', :action => 'show'
+ request.details_request '/details/request/:url_title', :action => 'details'
request.similar_request '/similar/request/:url_title', :action => 'similar'
request.describe_state '/request/:id/describe', :action => 'describe_state'
@@ -55,7 +56,8 @@ ActionController::Routing::Routes.draw do |map|
user.signin '/signin', :action => 'signin'
user.signup '/signup', :action => 'signup'
user.signout '/signout', :action => 'signout'
- user.signchange '/signchange', :action => 'signchange'
+ user.signchangepassword '/signchangepassword', :action => 'signchangepassword'
+ user.signchangeemail '/signchangeemail', :action => 'signchangeemail'
user.confirm '/c/:email_token', :action => 'confirm'
user.show_user '/user/:url_name', :action => 'show'
user.contact_user '/user/contact/:id', :action => 'contact'
@@ -66,8 +68,10 @@ ActionController::Routing::Routes.draw do |map|
map.with_options :controller => 'public_body' do |body|
body.list_public_bodies "/body", :action => 'list'
body.list_public_bodies "/body/list/:tag", :action => 'list'
+ body.list_public_bodies_redirect "/local/:tag", :action => 'list_redirect'
body.show_public_body "/body/:url_name", :action => 'show'
body.view_public_body_email "/body/:url_name/view_email", :action => 'view_email'
+ body.all_public_bodies_csv "/body/all-authorities.csv", :action => 'list_all_csv'
end
map.with_options :controller => 'comment' do |comment|
diff --git a/db/migrate/082_change_raw_email_to_binary.rb b/db/migrate/082_change_raw_email_to_binary.rb
index 2bcceee88..ec16d54a0 100644
--- a/db/migrate/082_change_raw_email_to_binary.rb
+++ b/db/migrate/082_change_raw_email_to_binary.rb
@@ -1,7 +1,7 @@
class ChangeRawEmailToBinary < ActiveRecord::Migration
def self.up
- change_column :raw_emails, :data, :binary, :null => true
- rename_column(:raw_emails, :data, :data_text)
+ change_column :raw_emails, :data, :text, :null => true # allow null
+ rename_column :raw_emails, :data, :data_text
add_column :raw_emails, :data_binary, :binary
end
diff --git a/db/migrate/083_add_indices_track_sent.rb b/db/migrate/083_add_indices_track_sent.rb
new file mode 100644
index 000000000..2b0908af8
--- /dev/null
+++ b/db/migrate/083_add_indices_track_sent.rb
@@ -0,0 +1,9 @@
+class AddIndicesTrackSent < ActiveRecord::Migration
+ def self.up
+ add_index :track_things_sent_emails, :created_at
+ end
+
+ def self.down
+ remove_index :track_things_sent_emails, :created_at
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c0782ac1d..2e6406ad7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -9,7 +9,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 82) do
+ActiveRecord::Schema.define(:version => 83) do
create_table "acts_as_xapian_jobs", :force => true do |t|
t.string "model", :null => false
@@ -220,6 +220,7 @@ ActiveRecord::Schema.define(:version => 82) do
t.datetime "updated_at"
end
+ add_index "track_things_sent_emails", ["created_at"], :name => "index_track_things_sent_emails_on_created_at"
add_index "track_things_sent_emails", ["track_thing_id"], :name => "index_track_things_sent_emails_on_track_thing_id"
create_table "user_info_request_sent_alerts", :force => true do |t|
diff --git a/lib/sendmail_return_path.rb b/lib/sendmail_return_path.rb
index f9ddba5b4..d8922f78b 100644
--- a/lib/sendmail_return_path.rb
+++ b/lib/sendmail_return_path.rb
@@ -1,7 +1,7 @@
# Monkeypatch!
# Grrr, semantics of smtp and sendmail send should be the same with regard to setting return path
-# See test in spec/lib/sendmail_return_path.rb
+# See test in spec/lib/sendmail_return_path_spec.rb
module ActionMailer
class Base
diff --git a/lib/tnef.rb b/lib/tnef.rb
new file mode 100644
index 000000000..ff88b0005
--- /dev/null
+++ b/lib/tnef.rb
@@ -0,0 +1,40 @@
+require 'tmpdir'
+
+class TNEF
+
+ # Extracts all attachments from the given TNEF file as a TMail::Mail object
+ # The TNEF file also contains the message body, but in general this is the
+ # same as the message body in the message proper.
+ def self.as_tmail(content)
+ main = TMail::Mail.new
+ main.set_content_type 'multipart', 'mixed', { 'boundary' => TMail.new_boundary }
+ Dir.mktmpdir do |dir|
+ IO.popen("/usr/bin/tnef -K -C #{dir}", "w") do |f|
+ f.write(content)
+ f.close
+ if $?.signaled?
+ raise IOError, "tnef exited with signal #{$?.termsig}"
+ end
+ if $?.exited? && $?.exitstatus != 0
+ raise IOError, "tnef exited with status #{$?.exitstatus}"
+ end
+ end
+ found = 0
+ Dir.new(dir).sort.each do |file| # sort for deterministic behaviour
+ if file != "." && file != ".."
+ file_content = File.open("#{dir}/#{file}", "r").read
+ attachment = TMail::Mail.new
+ attachment['content-location'] = file
+ attachment.body = file_content
+ main.parts << attachment
+ found += 1
+ end
+ end
+ if found == 0
+ raise IOError, "tnef produced no attachments"
+ end
+ end
+ main
+ end
+
+end
diff --git a/public/down.default.html b/public/down.default.html
index 58a28177a..5325c5243 100644
--- a/public/down.default.html
+++ b/public/down.default.html
@@ -62,17 +62,14 @@
<script type="text/javascript">
var pkBaseURL = (("https:" == document.location.protocol) ? "https://piwik.mysociety.org/" : "http://piwik.mysociety.org/");
document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));
-</script>
-<script type="text/javascript">
-<!--
-piwik_action_name = '';
-piwik_idsite = 4;
-piwik_url = pkBaseURL + "piwik.php";
-piwik_log(piwik_action_name, piwik_idsite, piwik_url);
-//-->
-</script>
-<noscript><p><img src="http://piwik.mysociety.org/piwik.php?i=1" style="border:0" alt=""></p></noscript>
-<!-- /Piwik -->
+</script><script type="text/javascript">
+try {
+var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 4);
+piwikTracker.trackPageView();
+piwikTracker.enableLinkTracking();
+} catch( err ) {}
+</script><noscript><p><img src="http://piwik.mysociety.org/piwik.php?idsite=4" style="border:0" alt=""/></p></noscript>
+<!-- End Piwik Tag -->
</body>
diff --git a/public/favicon.ico b/public/favicon.ico
index e69de29bb..26127495c 100644
--- a/public/favicon.ico
+++ b/public/favicon.ico
Binary files differ
diff --git a/public/images/README.txt b/public/images/README.txt
index fa20c3b97..0bf9f36d6 100644
--- a/public/images/README.txt
+++ b/public/images/README.txt
@@ -2,3 +2,5 @@ The mime type icons are copied from the following directory on an Ubuntu
desktop machine. Look there for new ones...
/usr/share/icons/Rodent/48x48/mimetypes
+
+Install package xfce4-icon-theme if the directory isn't there.
diff --git a/public/images/icon_application_vnd.openxmlformats-officedocument.presentationml.presentation_large.png b/public/images/icon_application_vnd.openxmlformats-officedocument.presentationml.presentation_large.png
new file mode 120000
index 000000000..4e0dcebe7
--- /dev/null
+++ b/public/images/icon_application_vnd.openxmlformats-officedocument.presentationml.presentation_large.png
@@ -0,0 +1 @@
+icon_application_vnd.ms-powerpoint_large.png \ No newline at end of file
diff --git a/public/images/icon_application_vnd.openxmlformats-officedocument.spreadsheetml.sheet_large.png b/public/images/icon_application_vnd.openxmlformats-officedocument.spreadsheetml.sheet_large.png
new file mode 120000
index 000000000..c664a30f9
--- /dev/null
+++ b/public/images/icon_application_vnd.openxmlformats-officedocument.spreadsheetml.sheet_large.png
@@ -0,0 +1 @@
+icon_application_vnd.ms-excel_large.png \ No newline at end of file
diff --git a/public/images/icon_application_vnd.openxmlformats-officedocument.wordprocessingml.document_large.png b/public/images/icon_application_vnd.openxmlformats-officedocument.wordprocessingml.document_large.png
new file mode 120000
index 000000000..950105e3b
--- /dev/null
+++ b/public/images/icon_application_vnd.openxmlformats-officedocument.wordprocessingml.document_large.png
@@ -0,0 +1 @@
+icon_application_vnd.ms-word_large.png \ No newline at end of file
diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css
index e7f70c594..ef6ed7adf 100644
--- a/public/stylesheets/main.css
+++ b/public/stylesheets/main.css
@@ -215,7 +215,7 @@ body
}
-/*------------------------------------------------ wrapper*/
+/*------------------------------------------------ wrapper round content */
#wrapper
{
@@ -223,13 +223,50 @@ body
clear: both;
top: 0px;
padding-top: 100px;
- margin: 0px auto 1.2em auto;
width: 53em;
+ margin: 0px auto 1.2em auto;
/* = 800px at default size? so 1em = 16px*/
text-align: left;
overflow: visible;
}
+/*------------------------------------------------ view as HTML */
+/* XXX this copies lines from #wrapper above, as didn't want to break wrapper
+ * right now */
+#wrapper_xlhtml {
+ position: relative;
+ clear: both;
+ top: 0px;
+ padding-top: 100px;
+ width: 90%;
+ margin: 0px auto 1.2em auto;
+ /* = 800px at default size? so 1em = 16px*/
+ text-align: left;
+ overflow: visible;
+}
+#view_html_content table {
+ border-collapse: collapse;
+ margin-bottom: 1em;
+}
+#view_html_content td, th {
+ border: solid 1px #000000;
+}
+#view_html_content td {
+ vertical-align: top
+}
+#view_html_content td {
+ max-width: 30em;
+ overflow: auto;
+}
+#view_html_content tr:nth-child(odd) {
+ background-color: #bbbbbb;
+}
+#view_html_content tr:nth-child(even) {
+ background-color: #dddddd;
+}
+
+
+
/*------------------------------------------------ content */
#content
@@ -511,7 +548,8 @@ dd { margin: 0.6em 0 2em 4em; width: 33em; }
{ background-image: url(../images/navimg/status-icons-succeed.png);}
/* Alert */
.icon_requires_admin,
- .icon_waiting_response_overdue
+ .icon_waiting_response_overdue,
+ .icon_waiting_response_very_overdue
{ background-image: url(../images/navimg/status-icons-attn.png);}
/* Postal */
.icon_gone_postal
@@ -1047,3 +1085,27 @@ div.act_link img {
font-size: 0.8em;
}
+/*------------------------------------------------ request details */
+
+#request_details table {
+ border-collapse: collapse;
+ margin-bottom: 1em;
+}
+#request_details td, th {
+ border: solid 1px #000000;
+}
+#request_details td {
+ vertical-align: top
+}
+#request_details td {
+ max-width: 30em;
+ overflow: auto;
+}
+#request_details tr.odd {
+ background-color: #bbbbbb;
+}
+#request_details tr.even {
+ background-color: #dddddd;
+}
+
+
diff --git a/script/clear-incoming-text-cache b/script/clear-caches
index 01ea95edc..01ea95edc 100755
--- a/script/clear-incoming-text-cache
+++ b/script/clear-caches
diff --git a/script/delete-old-post-redirects b/script/delete-old-things
index c1354e4f4..18e802ec7 100755
--- a/script/delete-old-post-redirects
+++ b/script/delete-old-things
@@ -4,4 +4,6 @@ LOC=`dirname $0`
$LOC/runner 'PostRedirect.delete_old_post_redirects()'
+$LOC/runner 'TrackThingsSentEmail.delete_old_track_things_sent_email()'
+
diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb
index e4404d4d0..b6d4e4c3f 100644
--- a/spec/controllers/request_controller_spec.rb
+++ b/spec/controllers/request_controller_spec.rb
@@ -263,7 +263,7 @@ describe RequestController, "when creating a new request" do
end
it "should accept a public body parameter" do
- get :new, :info_request => { :public_body_id => @body.id }
+ get :new, :public_body_id => @body.id
assigns[:info_request].public_body.should == @body
response.should render_template('new')
end
@@ -285,6 +285,15 @@ describe RequestController, "when creating a new request" do
response.should render_template('preview')
end
+ it "should allow re-editing of a request" do
+ post :new, :info_request => { :public_body_id => @body.id,
+ :title => "Why is your quango called Geraldine?"},
+ :outgoing_message => { :body => "This is a silly letter. It is too short to be interesting." },
+ :submitted_new_request => 1, :preview => 0,
+ :reedit => "Re-edit this request"
+ response.should render_template('new')
+ end
+
it "should redirect to sign in page when input is good and nobody is logged in" do
params = { :info_request => { :public_body_id => @body.id,
:title => "Why is your quango called Geraldine?"},
@@ -382,7 +391,7 @@ describe RequestController, "when making a new request" do
@user.stub!(:can_file_requests?).and_return(true)
User.stub!(:find).and_return(@user)
- @body = mock_model(PublicBody, :id => 314, :eir_only? => false, :is_requestable? => true)
+ @body = mock_model(PublicBody, :id => 314, :eir_only? => false, :is_requestable? => true, :name => "Test Quango")
PublicBody.stub!(:find).and_return(@body)
end
@@ -683,7 +692,11 @@ describe RequestController, "when classifying an information request" do
def request_url
"request/#{@dog_request.url_title}"
end
-
+
+ def unhappy_url
+ "help/unhappy/#{@dog_request.url_title}"
+ end
+
def expect_redirect(status, redirect_path)
post_status(status)
response.should redirect_to("http://test.host/#{redirect_path}")
@@ -691,16 +704,26 @@ describe RequestController, "when classifying an information request" do
it 'should redirect to the "request url" with a message in the right tense when status is updated to "waiting response" and the response is not overdue' do
@dog_request.stub!(:date_response_required_by).and_return(Time.now.to_date+1)
+ @dog_request.stub!(:date_very_overdue_after).and_return(Time.now.to_date+40)
+
expect_redirect("waiting_response", "request/#{@dog_request.url_title}")
flash[:notice].should match(/should get a response/)
end
it 'should redirect to the "request url" with a message in the right tense when status is updated to "waiting response" and the response is overdue' do
@dog_request.stub!(:date_response_required_by).and_return(Time.now.to_date-1)
+ @dog_request.stub!(:date_very_overdue_after).and_return(Time.now.to_date+40)
expect_redirect('waiting_response', request_url)
flash[:notice].should match(/should have got a response/)
end
-
+
+ it 'should redirect to the "request url" with a message in the right tense when status is updated to "waiting response" and the response is overdue' do
+ @dog_request.stub!(:date_response_required_by).and_return(Time.now.to_date-2)
+ @dog_request.stub!(:date_very_overdue_after).and_return(Time.now.to_date-1)
+ expect_redirect('waiting_response', unhappy_url)
+ flash[:notice].should match(/is long overdue/)
+ end
+
it 'should redirect to the "request url" when status is updated to "not held"' do
expect_redirect('not_held', request_url)
end
@@ -729,7 +752,7 @@ describe RequestController, "when classifying an information request" do
end
it 'should redirect to the "respond to last url" when status is updated to "gone postal"' do
- expect_redirect('gone_postal', "request/#{@dog_request.id}/response/1?gone_postal=1")
+ expect_redirect('gone_postal', "request/#{@dog_request.id}/response/#{@dog_request.get_last_response.id}?gone_postal=1")
end
it 'should redirect to the "request url" when status is updated to "internal review"' do
@@ -744,8 +767,8 @@ describe RequestController, "when classifying an information request" do
expect_redirect('error_message', "help/contact")
end
- it 'should redirect to the "request url" when status is updated to "user_withdrawn"' do
- expect_redirect('user_withdrawn', request_url)
+ it 'should redirect to the "respond to last url url" when status is updated to "user_withdrawn"' do
+ expect_redirect('user_withdrawn', "request/#{@dog_request.id}/response/#{@dog_request.get_last_response.id}")
end
end
@@ -775,7 +798,18 @@ describe RequestController, "when sending a followup message" do
response.should render_template('show_response')
end
+ it "should show preview when input is good" do
+ session[:user_id] = users(:bob_smith_user).id
+ post :show_response, :outgoing_message => { :body => "What a useless response! You suck.", :what_doing => 'normal_sort'}, :id => info_requests(:fancy_dog_request).id, :incoming_message_id => incoming_messages(:useless_incoming_message), :submitted_followup => 1, :preview => 1
+ response.should render_template('followup_preview')
+ end
+ it "should allow re-editing of a preview" do
+ session[:user_id] = users(:bob_smith_user).id
+ post :show_response, :outgoing_message => { :body => "What a useless response! You suck.", :what_doing => 'normal_sort'}, :id => info_requests(:fancy_dog_request).id, :incoming_message_id => incoming_messages(:useless_incoming_message), :submitted_followup => 1, :preview => 0, :reedit => "Re-edit this request"
+ response.should render_template('show_response')
+ end
+
it "should send the follow up message if you are the right user" do
# fake that this is a clarification
info_requests(:fancy_dog_request).set_described_state('waiting_clarification')
@@ -825,12 +859,16 @@ describe RequestController, "sending overdue request alerts" do
fixtures :info_requests, :info_request_events, :public_bodies, :users, :incoming_messages, :raw_emails, :outgoing_messages # all needed as integrating views
it "should send an overdue alert mail to creators of overdue requests" do
+ chicken_request = info_requests(:naughty_chicken_request)
+ chicken_request.outgoing_messages[0].last_sent_at = Time.now() - 30.days
+ chicken_request.outgoing_messages[0].save!
+
RequestMailer.alert_overdue_requests
deliveries = ActionMailer::Base.deliveries
deliveries.size.should == 1
mail = deliveries[0]
- mail.body.should =~ /promptly, as required by law/
+ mail.body.should =~ /promptly, as normally\s+required by law/
mail.to_addrs.to_s.should == info_requests(:naughty_chicken_request).user.name_and_email
mail.body =~ /(http:\/\/.*\/c\/(.*))/
@@ -845,7 +883,24 @@ describe RequestController, "sending overdue request alerts" do
assigns[:info_request].should == info_requests(:naughty_chicken_request)
end
- it "should send not actualy send the overdue alert if the user is banned" do
+ it "should include clause for schools when sending an overdue alert mail to creators of overdue requests" do
+ chicken_request = info_requests(:naughty_chicken_request)
+ chicken_request.outgoing_messages[0].last_sent_at = Time.now() - 30.days
+ chicken_request.outgoing_messages[0].save!
+
+ chicken_request.public_body.tag_string = "school"
+ chicken_request.public_body.save!
+
+ RequestMailer.alert_overdue_requests
+
+ deliveries = ActionMailer::Base.deliveries
+ deliveries.size.should == 1
+ mail = deliveries[0]
+ mail.body.should =~ /promptly, as normally\s+required by law during term time/
+ mail.to_addrs.to_s.should == info_requests(:naughty_chicken_request).user.name_and_email
+ end
+
+ it "should send not actually send the overdue alert if the user is banned" do
user = info_requests(:naughty_chicken_request).user
user.ban_text = 'Banned'
user.save!
@@ -856,6 +911,31 @@ describe RequestController, "sending overdue request alerts" do
deliveries.size.should == 0
end
+ it "should send a very overdue alert mail to creators of very overdue requests" do
+ chicken_request = info_requests(:naughty_chicken_request)
+ chicken_request.outgoing_messages[0].last_sent_at = Time.now() - 60.days
+ chicken_request.outgoing_messages[0].save!
+
+ RequestMailer.alert_overdue_requests
+
+ deliveries = ActionMailer::Base.deliveries
+ deliveries.size.should == 1
+ mail = deliveries[0]
+ mail.body.should =~ /required by law/
+ mail.to_addrs.to_s.should == info_requests(:naughty_chicken_request).user.name_and_email
+
+ mail.body =~ /(http:\/\/.*\/c\/(.*))/
+ mail_url = $1
+ mail_token = $2
+
+ session[:user_id].should be_nil
+ controller.test_code_redirect_by_email_token(mail_token, self) # XXX hack to avoid having to call User controller for email link
+ session[:user_id].should == info_requests(:naughty_chicken_request).user.id
+
+ response.should render_template('show_response')
+ assigns[:info_request].should == info_requests(:naughty_chicken_request)
+ end
+
end
describe RequestController, "sending unclassified new response reminder alerts" do
@@ -1031,6 +1111,92 @@ describe RequestController, "when viewing comments" do
end
+describe RequestController, "authority uploads a response from the web interface" do
+ fixtures :info_requests, :info_request_events, :public_bodies, :users
+
+ before(:all) do
+ # domain after the @ is used for authentication of FOI officers, so to test it
+ # we need a user which isn't at localhost.
+ @normal_user = User.new(:name => "Mr. Normal", :email => "normal-user@flourish.org",
+ :password => PostRedirect.generate_random_token)
+ @normal_user.save!
+
+ @foi_officer_user = User.new(:name => "The Geraldine Quango", :email => "geraldine-requests@localhost",
+ :password => PostRedirect.generate_random_token)
+ @foi_officer_user.save!
+ end
+
+ it "should require login to view the form to upload" do
+ @ir = info_requests(:fancy_dog_request)
+ @ir.public_body.is_foi_officer?(@normal_user).should == false
+ session[:user_id] = @normal_user.id
+
+ get :upload_response, :url_title => 'why_do_you_have_such_a_fancy_dog'
+ response.should render_template('user/wrong_user')
+ end
+
+ it "should let you view upload form if you are an FOI officer" do
+ @ir = info_requests(:fancy_dog_request)
+ @ir.public_body.is_foi_officer?(@foi_officer_user).should == true
+ session[:user_id] = @foi_officer_user.id
+
+ get :upload_response, :url_title => 'why_do_you_have_such_a_fancy_dog'
+ response.should render_template('request/upload_response')
+ end
+
+ it "should prevent uploads if you are not a requester" do
+ @ir = info_requests(:fancy_dog_request)
+ incoming_before = @ir.incoming_messages.size
+ session[:user_id] = @normal_user.id
+
+ # post up a photo of the parrot
+ parrot_upload = fixture_file_upload('parrot.png','image/png')
+ post :upload_response, :url_title => 'why_do_you_have_such_a_fancy_dog',
+ :body => "Find attached a picture of a parrot",
+ :file_1 => parrot_upload,
+ :submitted_upload_response => 1
+ response.should render_template('user/wrong_user')
+ end
+
+ it "should prevent entirely blank uploads" do
+ session[:user_id] = @foi_officer_user.id
+
+ post :upload_response, :url_title => 'why_do_you_have_such_a_fancy_dog', :body => "", :submitted_upload_response => 1
+ response.should render_template('request/upload_response')
+ flash[:error].should match(/Please type a message/)
+ end
+
+ # How do I test a file upload in rails?
+ # http://stackoverflow.com/questions/1178587/how-do-i-test-a-file-upload-in-rails
+ it "should let the requester upload a file" do
+ @ir = info_requests(:fancy_dog_request)
+ incoming_before = @ir.incoming_messages.size
+ session[:user_id] = @foi_officer_user.id
+
+ # post up a photo of the parrot
+ parrot_upload = fixture_file_upload('parrot.png','image/png')
+ post :upload_response, :url_title => 'why_do_you_have_such_a_fancy_dog',
+ :body => "Find attached a picture of a parrot",
+ :file_1 => parrot_upload,
+ :submitted_upload_response => 1
+
+ response.should redirect_to(:action => 'show', :url_title => 'why_do_you_have_such_a_fancy_dog')
+ flash[:notice].should match(/Thank you for responding to this FOI request/)
+
+ # check there is a new attachment
+ incoming_after = @ir.incoming_messages.size
+ incoming_after.should == incoming_before + 1
+
+ # check new attachment looks vaguely OK
+ new_im = @ir.incoming_messages[-1]
+ new_im.mail.body.should match(/Find attached a picture of a parrot/)
+ attachments = new_im.get_attachments_for_display
+ attachments.size.should == 1
+ attachments[0].filename.should == "parrot.png"
+ attachments[0].display_size.should == "94K"
+ end
+end
+
diff --git a/spec/controllers/track_controller_spec.rb b/spec/controllers/track_controller_spec.rb
index 7ab1c0a70..5959c2bc1 100644
--- a/spec/controllers/track_controller_spec.rb
+++ b/spec/controllers/track_controller_spec.rb
@@ -31,6 +31,11 @@ describe TrackController, "when sending alerts for a track" do
include LinkToHelper # for main_url
it "should send alerts" do
+ # set the time the comment event happened at to within the last week
+ ire = info_request_events(:silly_comment_event)
+ ire.created_at = Time.now - 3.days
+ ire.save!
+
TrackMailer.alert_tracks
deliveries = ActionMailer::Base.deliveries
@@ -41,12 +46,19 @@ describe TrackController, "when sending alerts for a track" do
mail.body =~ /(http:\/\/.*\/c\/(.*))/
mail_url = $1
mail_token = $2
-
+
mail.body.should_not =~ /&amp;/
+ mail.body.should_not include('sent a request') # request not included
+ mail.body.should_not include('sent a response') # response not included
+ mail.body.should include('added an annotation') # comment included
+
+ mail.body.should =~ /This a the daftest comment the world has ever seen/ # comment text included
+
# Check subscription managing link
-# XXX We can't do this, as it is redirecting to another control, so this is a
-# functional test. Bah, I so don't care, bit of an obsessive constraint.
+# XXX We can't do this, as it is redirecting to another controller. I'm
+# apparently meant to be writing controller unit tests here, not functional
+# tests. Bah, I so don't care, bit of an obsessive constraint.
# session[:user_id].should be_nil
# controller.test_code_redirect_by_email_token(mail_token, self) # XXX hack to avoid having to call User controller for email link
# session[:user_id].should == users(:silly_name_user).id
diff --git a/spec/controllers/user_controller_spec.rb b/spec/controllers/user_controller_spec.rb
index 109a95cbc..7fc9dbf98 100644
--- a/spec/controllers/user_controller_spec.rb
+++ b/spec/controllers/user_controller_spec.rb
@@ -84,6 +84,15 @@ describe UserController, "when signing in" do
response.should_not send_email
end
+# No idea how to test this in the test framework :(
+# it "should have set a long lived cookie if they picked remember me, session cookie if they didn't" do
+# get :signin, :r => "/list"
+# response.should render_template('sign')
+# post :signin, { :user_signin => { :email => 'bob@localhost', :password => 'jonespassword' } }
+# session[:user_id].should == users(:bob_smith_user).id
+# raise session.options.to_yaml # check cookie lasts a month
+# end
+
it "should ask you to confirm your email if it isn't confirmed, after log in" do
get :signin, :r => "/list"
response.should render_template('sign')
@@ -231,14 +240,14 @@ describe UserController, "when changing password" do
fixtures :users
it "should show the email form when not logged in" do
- get :signchange
- response.should render_template('signchange_send_confirm')
+ get :signchangepassword
+ response.should render_template('signchangepassword_send_confirm')
end
it "should send a confirmation email when logged in normally" do
session[:user_id] = users(:bob_smith_user).id
- get :signchange
- response.should render_template('signchange_confirm')
+ get :signchangepassword
+ response.should render_template('signchangepassword_confirm')
deliveries = ActionMailer::Base.deliveries
deliveries.size.should == 1
@@ -249,15 +258,15 @@ describe UserController, "when changing password" do
it "should send a confirmation email when have wrong login circumstance" do
session[:user_id] = users(:bob_smith_user).id
session[:user_circumstance] = "bogus"
- get :signchange
- response.should render_template('signchange_confirm')
+ get :signchangepassword
+ response.should render_template('signchangepassword_confirm')
end
it "should show the password change screen when logged in as special password change mode" do
session[:user_id] = users(:bob_smith_user).id
session[:user_circumstance] = "change_password"
- get :signchange
- response.should render_template('signchange')
+ get :signchangepassword
+ response.should render_template('signchangepassword')
end
it "should change the password, if you have right to do so" do
@@ -265,8 +274,8 @@ describe UserController, "when changing password" do
session[:user_circumstance] = "change_password"
old_hash = users(:bob_smith_user).hashed_password
- post :signchange, { :user => { :password => 'ooo', :password_confirmation => 'ooo' },
- :submitted_signchange_password => 1
+ post :signchangepassword, { :user => { :password => 'ooo', :password_confirmation => 'ooo' },
+ :submitted_signchangepassword_do => 1
}
users(:bob_smith_user).hashed_password.should != old_hash
@@ -297,16 +306,161 @@ describe UserController, "when changing password" do
end
+describe UserController, "when changing email address" do
+ integrate_views
+ fixtures :users
+
+ it "should require login" do
+ get :signchangeemail
+
+ post_redirect = PostRedirect.get_last_post_redirect
+ response.should redirect_to(:controller => 'user', :action => 'signin', :token => post_redirect.token)
+ end
+
+ it "should show form for changing email if logged in" do
+ @user = users(:bob_smith_user)
+ session[:user_id] = @user.id
+
+ get :signchangeemail
+
+ response.should render_template('signchangeemail')
+ end
+
+ it "should be an error if the password is wrong, everything else right" do
+ @user = users(:bob_smith_user)
+ session[:user_id] = @user.id
+
+ post :signchangeemail, { :signchangeemail => { :old_email => 'bob@localhost',
+ :password => 'donotknowpassword', :new_email => 'newbob@localhost' },
+ :submitted_signchangeemail_do => 1
+ }
+
+ @user.reload
+ @user.email.should == 'bob@localhost'
+ response.should render_template('signchangeemail')
+ assigns[:signchangeemail].errors[:password].should_not be_nil
+
+ deliveries = ActionMailer::Base.deliveries
+ deliveries.size.should == 0
+ end
+
+ it "should be an error if old email is wrong, everything else right" do
+ @user = users(:bob_smith_user)
+ session[:user_id] = @user.id
+
+ post :signchangeemail, { :signchangeemail => { :old_email => 'bob@moo',
+ :password => 'jonespassword', :new_email => 'newbob@localhost' },
+ :submitted_signchangeemail_do => 1
+ }
+
+ @user.reload
+ @user.email.should == 'bob@localhost'
+ response.should render_template('signchangeemail')
+ assigns[:signchangeemail].errors[:old_email].should_not be_nil
+
+ deliveries = ActionMailer::Base.deliveries
+ deliveries.size.should == 0
+ end
+
+ it "should work even if the old email had a case difference" do
+ @user = users(:bob_smith_user)
+ session[:user_id] = @user.id
+
+ post :signchangeemail, { :signchangeemail => { :old_email => 'BOB@localhost',
+ :password => 'jonespassword', :new_email => 'newbob@localhost' },
+ :submitted_signchangeemail_do => 1
+ }
+
+ response.should render_template('signchangeemail_confirm')
+ end
+
+ it "should send confirmation email if you get all the details right" do
+ @user = users(:bob_smith_user)
+ session[:user_id] = @user.id
+
+ post :signchangeemail, { :signchangeemail => { :old_email => 'bob@localhost',
+ :password => 'jonespassword', :new_email => 'newbob@localhost' },
+ :submitted_signchangeemail_do => 1
+ }
+
+ @user.reload
+ @user.email.should == 'bob@localhost'
+ @user.email_confirmed.should == true
+
+ response.should render_template('signchangeemail_confirm')
+
+ deliveries = ActionMailer::Base.deliveries
+ deliveries.size.should == 1
+ mail = deliveries[0]
+ mail.body.should include("confirm that you want to change")
+ mail.to.should == [ 'newbob@localhost' ]
+
+ mail.body =~ /(http:\/\/.*(\/c\/(.*)))/
+ mail_url = $1
+ mail_path = $2
+ mail_token = $3
+
+ # Check confirmation URL works
+ session[:user_id] = nil
+ session[:user_circumstance].should == nil
+ get :confirm, :email_token => mail_token
+ session[:user_id].should == users(:bob_smith_user).id
+ session[:user_circumstance].should == 'change_email'
+ response.should redirect_to(:controller => 'user', :action => 'signchangeemail', :post_redirect => 1)
+
+ # Would be nice to do a follow_redirect! here, but rspec-rails doesn't
+ # have one. Instead do an equivalent manually.
+ post_redirect = PostRedirect.find_by_email_token(mail_token)
+ post_redirect.circumstance.should == 'change_email'
+ post_redirect.user.should == users(:bob_smith_user)
+ post_redirect.post_params.should == {"submitted_signchangeemail_do"=>"1",
+ "action"=>"signchangeemail",
+ "signchangeemail"=>{
+ "old_email"=>"bob@localhost",
+ "new_email"=>"newbob@localhost",
+ "password"=>"jonespassword"},
+ "controller"=>"user"}
+ post :signchangeemail, post_redirect.post_params
+
+ response.should redirect_to(:controller => 'user', :action => 'show', :url_name => 'bob_smith')
+ flash[:notice].should match(/You have now changed your email address/)
+ @user.reload
+ @user.email.should == 'newbob@localhost'
+ @user.email_confirmed.should == true
+ end
+
+ it "should send special 'already signed up' mail if you try to change your email to one already used" do
+ @user = users(:bob_smith_user)
+ session[:user_id] = @user.id
+
+ post :signchangeemail, { :signchangeemail => { :old_email => 'bob@localhost',
+ :password => 'jonespassword', :new_email => 'silly@localhost' },
+ :submitted_signchangeemail_do => 1
+ }
+
+ @user.reload
+ @user.email.should == 'bob@localhost'
+ @user.email_confirmed.should == true
+
+ response.should render_template('signchangeemail_confirm')
+
+ deliveries = ActionMailer::Base.deliveries
+ deliveries.size.should == 1
+ mail = deliveries[0]
+
+ mail.body.should include("perhaps you, just tried to change their")
+ mail.to.should == [ 'silly@localhost' ]
+ end
+end
+
describe UserController, "when using profile photos" do
integrate_views
fixtures :users
- it "should not let you change profile photo if you're not logged in as the user"
+ it "should not let you change profile photo if you're not logged in as the user" do
user = users(:bob_smith_user)
data = load_file_fixture("parrot.png")
post :profile_photo, { :id => user.id, :data => data }
end
-
end
-
diff --git a/spec/fixtures/fake-authority-type.csv b/spec/fixtures/fake-authority-type.csv
new file mode 100644
index 000000000..4aa618ad1
--- /dev/null
+++ b/spec/fixtures/fake-authority-type.csv
@@ -0,0 +1,3 @@
+,North West Fake Authority,north_west_foi@localhost
+,Scottish Fake Authority,scottish_foi@localhost
+,Fake Authority of Northern Ireland,ni_foi@localhost
diff --git a/spec/fixtures/incoming-request-attach-attachments.email b/spec/fixtures/incoming-request-attach-attachments.email
new file mode 100644
index 000000000..efcf1a4d1
--- /dev/null
+++ b/spec/fixtures/incoming-request-attach-attachments.email
@@ -0,0 +1,54 @@
+From francis@localhost Tue Dec 22 14:34:49 2009
+From: Francis Irving <francis@localhost>
+To: test@localhost
+Subject: This is a test
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="V0207lvV8h4k8FAm"
+Content-Disposition: inline
+X-Mutt-Fcc: =uniheap
+Status: RO
+Content-Length: 852
+Lines: 42
+
+
+--V0207lvV8h4k8FAm
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+
+Attached is an email containing attachments.
+
+Francis
+
+--V0207lvV8h4k8FAm
+Content-Type: application/octet-stream; charset=us-ascii
+Content-Disposition: attachment; filename="incoming-request-two-same-name.email"
+
+From: EMAIL_FROM
+To: FOI Person <foi_person@localhost>
+Subject: Same attachment twice
+Content-Type: multipart/mixed; boundary="Q68bSM7Ycu6FN28Q"
+Content-Disposition: inline
+
+
+--Q68bSM7Ycu6FN28Q
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+
+
+
+--Q68bSM7Ycu6FN28Q
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: attachment; filename="hello.txt"
+
+Second hello
+
+--Q68bSM7Ycu6FN28Q
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: attachment; filename="hello.txt"
+
+First hello
+
+--Q68bSM7Ycu6FN28Q--
+
+
+--V0207lvV8h4k8FAm--
diff --git a/spec/fixtures/incoming-request-oft-attachments.email b/spec/fixtures/incoming-request-oft-attachments.email
new file mode 100644
index 000000000..13ba77680
--- /dev/null
+++ b/spec/fixtures/incoming-request-oft-attachments.email
@@ -0,0 +1,385 @@
+Date: Thu, 18 Feb 2010 02:00:20 +0000
+From: Public Authority <public@authority.gov.uk>
+To: request@whatdotheyknow.com
+Subject: Example of message with .oft attachment
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="6c2NcOVqGQ03X4Wi"
+Content-Disposition: inline
+User-Agent: Mutt/1.5.20 (2009-06-14)
+
+
+--6c2NcOVqGQ03X4Wi
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+
+Message body
+
+--6c2NcOVqGQ03X4Wi
+Content-Type: application/octet-stream
+Content-Disposition: attachment; filename="test.oft"
+Content-Transfer-Encoding: base64
+
+0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAAAgAAAAAA
+AAAAEAAACAAAAAIAAAD+////AAAAAAMAAAD/////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//////////////////////////////////9SAG8AbwB0ACAARQBuAHQAcgB5AAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAFAP//////////BAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwBerfPbDKAQkAAAAAIQAAAAAAAF8AXwBwAHIA
+bwBwAGUAcgB0AGkAZQBzAF8AdgBlAHIAcwBpAG8AbgAxAC4AMAAAAAAAAAAAAAAAAAAAAAAA
+AAAwAAIBDQAAAAYAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+DAAAAHACAAAAAAAAXwBfAG4AYQBtAGUAaQBkAF8AdgBlAHIAcwBpAG8AbgAxAC4AMAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAQH//////////x4AAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAFBx5d89sMoBMAXq3z2wygEAAAAAAAAAAAAAAABfAF8AcwB1AGIAcwB0AGcAMQAuADAA
+XwAwAEUAMAA0ADAAMAAxAEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgACAf////8KAAAA
+/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7///8AAAAAAAAAAAQA
+AAD9//////////////8FAAAABgAAAAcAAAALAAAAIwAAAAoAAAAVAAAADAAAAA0AAAAOAAAA
+DwAAABAAAAARAAAAEgAAABMAAAAUAAAA/v///xYAAAAXAAAAGAAAABkAAAAaAAAAGwAAABwA
+AAAdAAAAHgAAAB8AAAAgAAAAIQAAACIAAAAkAAAA/v////7/////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////UgBvAG8AdAAgAEUAbgB0AHIAeQAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYABQD//////////wQAAABG8AYA
+AAAAAMAAAAAAAABGAAAAAAAAAAAAAAAAEJnu3z2wygEJAAAAACEAAAAAAABfAF8AcAByAG8A
+cABlAHIAdABpAGUAcwBfAHYAZQByAHMAaQBvAG4AMQAuADAAAAAAAAAAAAAAAAAAAAAAAAAA
+MAACAQ0AAAAGAAAA/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwA
+AABwAgAAAAAAAF8AXwBuAGEAbQBlAGkAZABfAHYAZQByAHMAaQBvAG4AMQAuADAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAoAAEB//////////8eAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AABQceXfPbDKATAF6t89sMoBAAAAAAAAAAAAAAAAXwBfAHMAdQBiAHMAdABnADEALgAwAF8A
+MABFADAANAAwADAAMQBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAgH/////CgAAAP//
+//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+////AAAAAAAAAAD/////
+/////wQAAAD9////BQAAAAYAAAAHAAAACwAAACMAAAAKAAAAFQAAAAwAAAANAAAADgAAAA8A
+AAAQAAAAEQAAABIAAAATAAAAFAAAAP7///8WAAAAFwAAABgAAAAZAAAAGgAAABsAAAAcAAAA
+HQAAAB4AAAAfAAAAIAAAACEAAAAiAAAAJAAAAP7////+////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+/////////////////////////////18AXwBzAHUAYgBzAHQAZwAxAC4AMABfADAARQAwADMA
+MAAwADEARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAIBBwAAAAsAAAD/////AAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v///wAAAAAAAAAAXwBfAHMAdQBiAHMA
+dABnADEALgAwAF8AMABFADAAMgAwADAAMQBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoA
+AgD///////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+////
+AAAAAAAAAABfAF8AYQB0AHQAYQBjAGgAXwB2AGUAcgBzAGkAbwBuADEALgAwAF8AIwAwADAA
+MAAwADAAMAAwADAAAAAAAAAAPAABAf//////////FgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+8Pfm3z2wygGQfujfPbDKAQAAAAAAAAAAAAAAAF8AXwBzAHUAYgBzAHQAZwAxAC4AMABfADAA
+MAAxAEEAMAAwADEARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAIBAgAAABEAAAD/////
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAgAAAAAAAAAXwBfAHMA
+dQBiAHMAdABnADEALgAwAF8AMAAwADMANwAwADAAMQBFAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAACoAAgH///////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAKAAAABAAAAAAAAABfAF8AcwB1AGIAcwB0AGcAMQAuADAAXwAwADAANwAwADAAMAAxAEUA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgACAf////8FAAAA/////wAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAEAAAAAAAAAF8AXwBzAHUAYgBzAHQAZwAxAC4A
+MABfADAARQAxAEQAMAAwADEARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAIA////////
+////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAA
+XwBfAHMAdQBiAHMAdABnADEALgAwAF8AMQAwADAAMAAwADAAMQBFAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAACoAAgADAAAAAQAAAP////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAIAAAACgAAAAAAAABfAF8AcwB1AGIAcwB0AGcAMQAuADAAXwAxADAAMAA5ADAA
+MQAwADIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgACAf///////////////wAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAACOAAAAAAAAAF8AXwBzAHUAYgBzAHQA
+ZwAxAC4AMABfADMAMAAwAEIAMAAxADAAMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAIA
+DAAAAA8AAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAABAA
+AAAAAAAAXwBfAHMAdQBiAHMAdABnADEALgAwAF8AOAAwADAAMwAwADAAMQBFAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAACoAAgD///////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAADAAAABAAAAAAAAABfAF8AcwB1AGIAcwB0AGcAMQAuADAAXwA4ADAA
+MAA4ADAAMAAxAEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgACAQ4AAAAQAAAA/////wAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAGAAAAAAAAAF8AXwBzAHUA
+YgBzAHQAZwAxAC4AMABfADgAMAAwADkAMAAwADEARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAqAAIA////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AQAAAAYAAAAAAAAAXwBfAHMAdQBiAHMAdABnADEALgAwAF8AMAAwADMARAAwADAAMQBFAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAgAIAAAACQAAAP////8AAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAD+////AAAAAAAAAABfAF8AcAByAG8AcABlAHIAdABpAGUA
+cwBfAHYAZQByAHMAaQBvAG4AMQAuADAAAAAAAAAAAAAAAAAAAAAAAAAAMAACAP//////////
+/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEAAAB4AQAAAAAAAF8A
+XwBzAHUAYgBzAHQAZwAxAC4AMABfADAARgBGADkAMAAxADAAMgAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAqAAIB////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAcAAAAAQAAAAAAAAA/v////7////+/////v////7///8GAAAABwAAAP7////+////
+/v////7////+////DQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAA/v////7/
+///+/////v////7////+/////v////7////+/////v////7////+/////v////7////+////
+/v////7////+/////v////7////+/////v////7////+/////v////7////+/////v////7/
+///+/////v///zUAAAA2AAAANwAAADgAAAA5AAAAOgAAADsAAAA8AAAAPQAAAD4AAAA/AAAA
+QAAAAEEAAABCAAAAQwAAAEQAAABFAAAARgAAAEcAAABIAAAASQAAAEoAAABLAAAATAAAAE0A
+AABOAAAATwAAAFAAAABRAAAAUgAAAFMAAABUAAAAVQAAAFYAAABXAAAAWAAAAFkAAABaAAAA
+WwAAAFwAAABdAAAAXgAAAF8AAABgAAAAYQAAAGIAAABjAAAAZAAAAGUAAABmAAAAZwAAAGgA
+AABpAAAAagAAAP7////+/////v////7////+/////v////7///9yAAAAcwAAAHQAAAB1AAAA
+dgAAAP7///94AAAAeQAAAP7///97AAAAfAAAAH0AAAB+AAAAfwAAAIAAAAB0ZXN0AAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+YXR0YWNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAGF0dGFjaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAxMS4wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWOJahBLomUW+U1Xfqq/BDwAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoAAAAPAQAATFpGdYsT
+VYIDAAoAcmNwZzEyNeIyA0N0ZXgFQQEDAff/CoACpAPkBxMCgA/zAFAEVj8IVQeyESUOUQMB
+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
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqFAAAGACUAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5hQAABgAiAJqF
+AAAGAD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+JIUAAAYAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAABOFAAAGABsAl4UAAAYAOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAbhQAABgAaADqFAAAGACMAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQoUAAAYAGACEhQAABgA1AAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWFAAAGABYANIUAAAYA
+IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUhQAA
+BgAVADeFAAAGACcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAEYUAAAYAFAAwhQAABgAkAJWFAAAGADoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAB2FAAAGABEAkYUAAAYAPAAJiAAACgBEAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAchQAABgAQAJCFAAAGADkACIgAAAoAQwAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4UAAAYADwA2hQAABgAmAEmF
+AAAGACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaFAAAGAA4A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AABghQAABgANAA+FAAAGABkAQYUAAAYAHgCDhQAABgAyAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAoUAAAYADABEhQAABgAqAIaFAAAGADMAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFGFAAAGAAsAH4UAAAYAEwBwhQAABgAtAJOFAAAGADgA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQhQAABgAKAB6FAAAGABIAZY8A5gkA
+PgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAj4UAAAYACAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiF
+AAAGAAcAWbhQBAkAPQCchQAABgBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAOhQAABgAGAECFAAAGAB0AgoUAAAYAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAYUAAAYABQAghQAABgAcAEOFAAAGACkAhYUAAAYANAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaFAAAGAAQAGYUAAAYAFwA4hQAABgAoAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUhQAABgADAJaFAAAGAAkA
+NYUAAAYAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIUAAAYA
+AQBShQAABgACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAOFAAAGAAAARYUAAAYAKwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAABAAkAAAPcBgAAAAAhBgAAAAAFAAAACQIAAAAABQAAAAEC////AKUA
+AABBC8YAiAAgACAAAAAAACAAIAAAAAAAKAAAACAAAABAAAAAAQABAAAAAAAAAQAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////4H///8AB//
+/AAA//wAAH/8AAB//AAAf/wAAH/8AAB//AAAf/wAAH/8AAB//AAAf/wAAH/8AAB//AAAf/wA
+AH/8AAB//AAAf/wAAH/8AAB//AAAf/wAAH/8AAB//AAAf/wAAH/8AAB//AAA//5mSf//////
+/////yEGAABBC0YAZgAgACAAAAAAACAAIAAAAAAAKAAAACAAAAAgAAAAAQAYAAAAAAAADAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAHNxc3Nxc3Nxc3Nxc3Nxc3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6W
+lO/f1vf35/f359bXztbXzsbPxmtpa2tpa2tpa2tpa2tpa3NxcwAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6WlO/f1vf35/f35/f3
+5/f37/f37/f37/f37/f37/f379bXzs7PxnNxc2tpa2tpa2tpa2tpawAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/f1vf353t5c2tpY3t5c3t5c2tpY/f3
+7/f37/f37/f39/f39/f/9///99bXzs7Pxs7PxnNxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAM6OjO/n1vf35/f35/f37/f37/f37/f37/f37/f37/f37/f39/f/
+9///9///9///9///9////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAM6OjO/n1vf353t5c3t5c3t5c2tpY5yelPf37/f37/f39/f/9/f/9///9///9///9///
+/////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/n1vf3
+7/f37/f37/f37/f37/f37/f37/f39/f/9/f/9///9///9///9////////////3NxcwAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/n3vf37/f37/f37/f37/f3
+7/f37/f39/f39/f/9///9///9///9///9////////////3NxcwAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/n3vf37/f37/f37/f37/f37/f37/f39/f/9///
+9///9///9///9////////////////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAM6OjO/n3ufn3rW+tdbXzvf37/f37/f39/f/9/f/9///9///9///9///////
+/////////////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6O
+jO/n3ufn3rW+tYyOhHt5c2tpa3t5e3t5e2tpa3t5e5yenL2+vb2+vf///////////////3Nx
+cwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/n3vf37/f37/f3
+7/f39/f39/f/9///9///9///987Pzr2+va2urXt5e////////////3NxcwAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/n3vf373t5c2tpa3t5e3t5e2tpa3t5
+e4yOjL2+vb2+ve/v7////////////////////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAM6OjO/n3vf37/f37/f39/f/9/f/9///9///997f3r2+vb2+vXt5
+e3t5e3Nxc97f3v///////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAM6OjO/n3vf375yelHt5e2tpa3t5e3t5e62urb2+vb2+vf//////////////////////
+/////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/n3vf3
+9/f39/f/9///9///9///972+vb2+va2urXt5e3t5e3Nxc5yenP///////////3NxcwAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/n3vf39/f/9///9///9///
+9///9////////////////////////////////////////3NxcwAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/n3vf/9/f/9///9///9///9///////////////
+/////////////////////////////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAM6OjO/n3vf/972+tb2+tc7Pzv//////////////////////////////////
+/////////////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6O
+jO/n3v//972+tb2+vZyenHt5e3t5e97f3v///////////////////////////////////3Nx
+cwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjO/n5///997f1v//
+9////////////////////////////////////////////////////3NxcwAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjPfn5///94yOjHt5e3t5e3t5e4yOjP//
+/////////////////////////////////////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAM6OjPfn5///9///////////////////////////////////////
+/////////////////////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAM6OjPfn54yGlK2+xv///97HzrXHzv/39//39+/X1v////////fv7///////////////
+/////3NxcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6OjOfPzkJp
+hBiWve/PzkJphCmmxrWute/f3kJphFqWrf///4Rpe0KOrfff3kJphEKmxu/n73NxcwAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANaenL2GjMZxcwCexr1pa71pa1qG
+lIxpe8Zxc72GjDGOrdaWlNaWlDGWtb2epdaenFKetYymtXNxcwAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFqmtQiuzjnH1lqmtVqmtUq+1hjH1lqmtVqmtWumtTmu
+vXOGlL2WnBCmvZR5jK15hFKOpVKWpQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAFqmtVqmtQAAAAAAAAi2zlqmtQAAAAAAAErP3gi2zgAAACm2zmO+zgAA
+AAi2zgiuzgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAYXR0YWNoLnR4dAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF0dGFjaC50eHQAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAudHh0AAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+VGVzdCBhdHRhY2htZW50IA0KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAGF0dGFjaC50eHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADACEOAgAAAAAAAACi2AgA
+AwD+DwIAAAAHAAAAAAAAAAMA9A8CAAAAAgAAAHMuADEDAPcPAgAAAAAAAABvY3VtAwAFNwcA
+AAABAAAAAAAAAAMACzcHAAAA/////wAAAAACAfkPAgAAAAQAAAADAFYGHgABMAYAAAALAAAA
+AwAAAEAABzAGAAAAABiYXj2wygFAAAgwBgAAAAAYmF49sMoBAgEBNwYAAAASAAAAAwDuAAIB
+AjcGAAAAAAAAAAMA7gAeAAM3BgAAAAUAAAADAAAAHgAENwYAAAALAAAAAwAAAB4ABzcGAAAA
+CwAAAAMAAAACAQk3BgAAALgNAAADAO8AAwAUNwYAAAAAAAAAAAAAAAMA+n8GAAAAAAAAAAAA
+AABAAPt/BgAAAABA3aNXRbMMQAD8fwYAAAAAQN2jV0WzDAMA/X8GAAAAAAAAAAAAAAALAP5/
+BgAAAAAAAAAAAAAACwD/fwYAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAGMAbwBuAHQAZQBuAHQA
+LQB0AHkAcABlABoAAABjAG8AbgB0AGUAbgB0AC0AYwBsAGEAcwBzAAAAZAAAAGgAdAB0AHAA
+OgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBvAHUA
+dABsAG8AbwBrAC8AcABoAGkAcwBoAGkAbgBnAHMAdABhAG0AcAAQAAAASwBlAHkAdwBvAHIA
+ZABzAAAAAAAAAAAAA4UAAAYAAAAQhQAABgABAFKFAAAGAAIAVIUAAAYAAwAGhQAABgAEAAGF
+AAAGAAUADoUAAAYABgAYhQAABgAHAI+FAAAGAAgAloUAAAYACQBQhQAABgAKAFGFAAAGAAsA
+AoUAAAYADABghQAABgANABaFAAAGAA4AF4UAAAYADwAchQAABgAQAB2FAAAGABEAHoUAAAYA
+EgAfhQAABgATABGFAAAGABQAFIUAAAYAFQAVhQAABgAWABmFAAAGABcAQoUAAAYAGAAPhQAA
+BgAZABuFAAAGABoAE4UAAAYAGwAghQAABgAcAECFAAAGAB0AQYUAAAYAHgAkhQAABgAfADSF
+AAAGACAANYUAAAYAIQA5hQAABgAiADqFAAAGACMAMIUAAAYAJAAahQAABgAlADaFAAAGACYA
+N4UAAAYAJwA4hQAABgAoAEOFAAAGACkARIUAAAYAKgBFhQAABgArAEmFAAAGACwAcIUAAAYA
+LQCAhQAABgAuAIGFAAAGAC8AgQAAAIIAAAD+/////v//////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////+ChQAABgAwAHiF
+AAAGADEAg4UAAAYAMgCGhQAABgAzAIWFAAAGADQAhIUAAAYANQCMhQAABgA2AI6FAAAGADcA
+k4UAAAYAOACQhQAABgA5AJWFAAAGADoAl4UAAAYAOwCRhQAABgA8AAAAAAAJAD0AHAAAAAkA
+PgCahQAABgA/AJuFAAAGAEAAnIUAAAYAQQA8AAAABQBCAAiIAAAKAEMACYgAAAoARACkAAAA
+BQBFAAAAAAAAAAAAAAAAAAAAAAAIIAYAAAAAAMAAAAAAAABGhgMCAAAAAADAAAAAAAAARgsg
+BgAAAAAAwAAAAAAAAEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAA==
+
+--6c2NcOVqGQ03X4Wi--
+
diff --git a/spec/fixtures/incoming-request-tnef-attachments.email b/spec/fixtures/incoming-request-tnef-attachments.email
new file mode 100644
index 000000000..db93c1b8f
--- /dev/null
+++ b/spec/fixtures/incoming-request-tnef-attachments.email
@@ -0,0 +1,3024 @@
+From: public@authority.gov.uk
+To: request@whatdotheyknow.com
+Subject: Example of message with TNEF attachment
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="zhXaljGHf11kAtnf"
+Content-Disposition: inline
+Status: RO
+
+
+--zhXaljGHf11kAtnf
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+
+Message body
+
+--zhXaljGHf11kAtnf
+Content-Type: application/ms-tnef
+Content-Transfer-Encoding: base64
+
+eJ8+IjULAQaQCAAEAAAAAAABAAEAAQeQBgAIAAAA5AQAAAAAAADoAAEIgAcAGAAAAElQTS5N
+aWNyb3NvZnQgTWFpbC5Ob3RlADEIAQ2ABAACAAAAAgACAAELgAEAIQAAADRBMkVDQTA0QzM5
+RTI1NEY4RUJENkNGRjhCNTY2MUU0AHYHAQSAAQBQAAAARk9JIFJlcXVlc3QgRi0yMDA5LTAy
+OTc2RnVsbCBSZXNwb25zZSAoTk9UIFBST1RFQ1RJVkVMWSBNQVJLRUQgLSBOTyBERVNDUklQ
+VE9SKQClFgEFgAMADgAAANkHDAADAAsAJQA0AAQAVwEBIIADAA4AAADZBwwAAwALACUANAAE
+AFcBAQmAAQAhAAAANEEyRUNBMDRDMzlFMjU0RjhFQkQ2Q0ZGOEI1NjYxRTQAdgcBA5AGAEgX
+AAAqAAAAAwAmAAAAAAADADYAAAAAAEAAOQC6KWYMDXTKAR4APQABAAAAAQAAAAAAAAACAUcA
+AQAAADgAAABjPUdCO2E9IDtwPUh1bWJlcnNpZGUgUG9saWM7bD1aRVVTLTA5MTIwMzExMzc1
+MlotMTE0NDQzAB4AcAABAAAAUAAAAEZPSSBSZXF1ZXN0IEYtMjAwOS0wMjk3NkZ1bGwgUmVz
+cG9uc2UgKE5PVCBQUk9URUNUSVZFTFkgTUFSS0VEIC0gTk8gREVTQ1JJUFRPUikAAgFxAAEA
+AAAWAAAAAcp0DOJ8BIc5Rar2QGqJaIOltSQ+BgAAHgAaDAEAAAASAAAAV2FsbGVyLCBQYXVs
+IDgwNjUAAAAeAB0OAQAAAFAAAABGT0kgUmVxdWVzdCBGLTIwMDktMDI5NzZGdWxsIFJlc3Bv
+bnNlIChOT1QgUFJPVEVDVElWRUxZIE1BUktFRCAtIE5PIERFU0NSSVBUT1IpAAIBCRABAAAA
+zBEAAMgRAABUJwAATFpGdf+8qe4DAAoAcmNwZzEyNeIyA0N0ZXgFQQEDAfcnAqQD4wIAY2gK
+wHNl+HQwIAcTAoMAUARWCFXfB7ICgw5RAwEQpzIGAAbD3QKDMwRGEKkSnjQQPwIA2HBycRSA
+EVg1A0UT+J8YtRSmApEI7wn3IDsJb/0OMDUdoB2kHtEJtB+CHpfnApELAgMwdWMAUAsJAWTM
+MzYRMAumIFkIYQfwlQEQOgqjTyO1Ri0B0AAwOS0wMjk3NqcKogqjJWAgRAWQZQbQeQSQLCAl
+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
+0AJB/wQROqMVb3gKF0L9Am4BOSJ/AbEeAxfKWOESkUD4BwAt/xshOAAYUadQ3BJFlxWvFrX/
+Y49axVmhKTBZMCZSaRMKg/9lyI1vHJE9QUHSYJMHURVvg8xdAAEwMTQ4MtCQ4Dc4MzEzJ3KL
+kRcAfwJgAjAE4ClhE3ZdIhASOv/NNNj2MY8ymMofza/Ov8/P/QvjRQLwAOA2UBWpekQyopMP
+wAIgQGg5Vy5wlHDbQDByJHW24AunUmkEBuD7W4LfZ2LWJBWvJNZSQUtC/1DxgaNc+XY5NlXZ
+fxZ5C+T+eVehdGG4QUuhC+QDwQWAfkzgAQvkAOCVUAtBC+NDjwHARRBGwQvjU0s5M8B8QUbN
+NRdQFvA2UDNhNooyPOE0POA3MDA2ZLp3UCAuAqkRMSsYZ1Rwn0DhC6YLplJOC6J9ewuiVQFQ
+XB2gZvUxMTQQNAMLoSjBdzEyMjQw4VW0aDE1OFZRAvCkcNhsMThPoFczdDOAVxXecleXVWBY
+QlSyXAIgADCTVmERclxmVaBmc08A/CAqW19bX1tfW1YOOBGg/zShCBHkUD2SGuISkhAxRYH+
+dKWRh4IG0SGxB0Dy8Qvj/wOACODpc1ESP0ETsCLEOU//YqQAAAIgAtAuUe9CX70xAd/qAfXh
+COIwwq/hLrl4GkL/Y4sa4hGRHYKt0wGmHoIK8P0K0CkL4wAAA/LFZRKR1gb/ASJ4lOnEh5Md
+cAcgAtBo4/0y4WEc0BdQBbABIAFQjOL7J4ECEHNKALyw+hIVNWh13xdyC+NFgQ/AEXB3KTEE
+EPxPcB/BCpL4MWwjb5P6Vf9iYAOAEaBipBeREpJFgRTD/yABvJE6EQQAaPkI4EEAAXL/xCko
+pSLSt8hkjwu2W19bX0NbX16PIH19AIIwHgA1EAEAAAA8AAAAPHh4eHh4eHh4eHh4eHh4eHh4
+eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHhAeHh4eC54eHh4eHh4Lnh4eD4ACwDyEAEAAAAfAPMQ
+AQAAAKgAAABGAE8ASQAgAFIAZQBxAHUAZQBzAHQAIABGAC0AMgAwADAAOQAtADAAMgA5ADcA
+NgBGAHUAbABsACAAUgBlAHMAcABvAG4AcwBlACAAKABOAE8AVAAgAFAAUgBPAFQARQBDAFQA
+SQBWAEUATABZACAATQBBAFIASwBFAEQAIAAtACAATgBPACAARABFAFMAQwBSAEkAUABUAE8A
+UgApAC4ARQBNAEwAAAALAPYQAAAAAEAABzCKiocMDXTKAUAACDCKiocMDXTKAQMA3j+fTgAA
+AwDxPwkEAAAeAPg/AQAAABIAAABXYWxsZXIsIFBhdWwgODA2NQAAAAIB+T8BAAAAWwAAAAAA
+AADcp0DIwEIQGrS5CAArL+GCAQAAAAAAAAAvTz1IVU1CRVJTSURFIFBPTElDRS9PVT1OT1JU
+SEJBTksvQ049UkVDSVBJRU5UUy9DTj1QQVVMLldBTExFUgAAHgD6PwEAAAAVAAAAU3lzdGVt
+IEFkbWluaXN0cmF0b3IAAAAAAgH7PwEAAAAeAAAAAAAAANynQMjAQhAatLkIACsv4YIBAAAA
+AAAAAC4AAAADAP0/5AQAAAMAGUAAAAAAAwAaQAAAAAAeADBAAQAAAAwAAABQQVVMLldBTExF
+UgAeADFAAQAAAAwAAABQQVVMLldBTExFUgAeADhAAQAAAAwAAABQQVVMLldBTExFUgAeADlA
+AQAAAAIAAAAuAAAAAwB2QP////8DAAlZAQAAAAsA/oIIIAYAAAAAAMAAAAAAAABGAAAAAIKF
+AAAAAAAAHgAOgwggBgAAAAAAwAAAAAAAAEYAAAAAj4UAAAEAAAAOAAAARk9JIDA5LTAyOTc2
+aQAAAB4AFIMIIAYAAAAAAMAAAAAAAABGAAAAAJaFAAABAAAADgAAAEZPSSAwOS0wMjk3NmkA
+AAALACkAAAAAAAsAIwAAAAAAAwAGEKuYvFYDAAcQZxoAAAMAEBAAAAAAAwAREAAAAAAeAAgQ
+AQAAAGUAAABZT1VSUkVGOk9VUlJFRjpGLTIwMDktMDI5NzYwMkRFQ0VNQkVSLDIwMDlERUFS
+TVJXQUxLRVIsVEhBTktZT1VGT1JZT1VSUkVRVUVTVEZPUklORk9STUFUSU9OREFURUQwM05P
+AAAAAAIBfwABAAAAPAAAADx4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
+eHh4QHh4eHgueHh4eHh4eC54eHg+AA9nAgKQBgAOAAAAAQD/////IAAgAAAAAAA9BAISgAMA
+DgAAANkHDAACAAwAOAANAAMAQgECE4ADAA4AAADZBwwAAwALACUANAAEAFcBAhCAAQASAAAA
+Rk9JIDA5LTAyOTc2aS5kb2MAaQQCEYAGALgNAAABAAkAAAPcBgAAAAAhBgAAAAAFAAAACQIA
+AAAABQAAAAEC////AKUAAABBC8YAiAAgACAAAAAAACAAIAAAAAAAKAAAACAAAABAAAAAAQAB
+AAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/
+/////AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAABgAAA
+AYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAOA
+AAAHgAAAD/wAAB/8AAA//////yEGAABBC0YAZgAgACAAAAAAACAAIAAAAAAAKAAAACAAAAAg
+AAAAAQAYAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALeik498anhhTGpTPGZON2JK
+MmBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBI
+MGBIMAAAAAAAAAAAAAAAAAAAAAAAAAAAALijlPro3d3LwtvDtdy7qty2oOGwluOxjuOxjuOx
+juOwjOSuieWthuerg+iqgOmof+qle+yjd+2hc++fb++ea/GcaPGbZfKZY2BIMAAAAAAAAAAA
+AAAAAAAAAAAAAAAAALijlPrp3/rp3/rp3/rp3vno3fnn3fnn3Pnn2/jm2vnl2vjk2Pjk1/jj
+1fji1Pjh0vjg0ffe0PfezffczPfbyvfayPfZxvKaZWBIMAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ALmklfrs4vnr4vrr4vnq4vnq4frp4Pnp4Pnp3vno3fnn3fnn2/nm2vjl2Pjk2Pji1vji1Pfg
+0vjf0fjez/fdzffcy/fayPGbZ2BIMAAAAAAAAAAAAAAAAAAAAAAAAAAAALmllvrt5frt5fnt
+5Prt5Prt5Prr4/rr4vrr4vrq4frp4Pro3/ro3fno3Pnm2vnl2fjk2Pjj1fji0/jg0fffz/je
+zvjdzPCcamBIMAAAAAAAAAAAAAAAAAAAAAAAAAAAALqmlvrv6Prv5/rv6Pru5/ru5/ru5vvt
+5vrt5frs5Prs4/rr4vrq4frq39GnidCmh8+khc+jhM6igs2hgfjh0/jf0Pjezu+ebWBIMAAA
+AAAAAAAAAAAAAAAAAAAAAAAAALunmPvx6/vx6/vx6vvw6vvx6fvw6frw6Pvv6Pvv5/ru5vrt
+5frs5Prr4vrr4frp3/no3fnn2/nm2vjk1/ji1fjh1Pjg0e6gcGBIMAAAAAAAAAAAAAAAAAAA
+AAAAAAAAALyomfvz7fvy7eG/puC9o9+7od25n9y3nNu1mtmzmNixldevk9aukdWsj9SqjdOp
+i9KnidCmh9Ckhs+jhPnl2Pjj1vjh1O2hc2BIMAAAAAAAAAAAAAAAAAAAAAAAAAAAAL2pmvz0
+7/v07/v07/v07/v07/zz7vvz7vvz7vvy7fvx7Pvx6/vw6vrv6Pru5/rt5frs4/rr4vrp4Pro
+3fnm2/nl2Pjj1uujd2BIMAAAAAAAAAAAAAAAAAAAAAAAAAAAAL6qnPv28fv18eTDq+PBqeG/
+puC9pN+7od25n9y3ndu1mtq0mNiyltewlNaukdWsj9SrjdOpi9KoidGmiPno3vnn2/nl2eul
+e2BIMAAAAAAAAAAAAAAAAAAAAAAAAAAAAL+snPz38/z38/z38/z28/z28/z28/z28/v18vz1
+8fz18fv18Pv07/vz7vvy7Pvx6/rw6frv5/rt5frs4vnq4Pno3vnm2+mnf2BIMAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAMCsnfz49fz49ebHr+XFreTDq+PBqeHApuC+pN+8ot66n9y4ndu2mtq0
+mNiyltewlNauktWsj9SrjdOpjPrs4/rq4fno3eepgWBIMAAAAAAAAKxgN6lcMalcMaJZK6JZ
+K51SKJhLIJhLII1EFY1EFY1EFYY7B4Y7B4A1BIA1BHcxAPz39Pz28/z28vz18fv07/vz7vvy
+6/vw6frv5/rt5frr4vrq4OashWBIMAAAAAAAALdpQ+SpkeSpkeSpkeSpkeejh9uiiduiidia
+gdaVe9aVe8eOdMeOdMeOdMeOdIA1BN+8ot66oNy4ndu2m9q0mdmyltewlNauktWtkPrv5/vt
+5frr4uStiWBIMAAAAAAAALdpQ+uvl/r18/jy8Pjw7vjw7vbu7Pft6vbs6Pbq5/bo5vXn5PXm
+4vXm4seOdIA1BPz59/z59/z49vz39Pz28/v18fv07/vy7fvx6/vw6fvu5vrs5OOvjWBIMAAA
+AAAAAL9uSuuvl/r18/rz8vrz8vjw7vjw7vbu7Pft6vbs6Pbq5/bo5vXn5PXm4seOdIY7B+LA
+p+C+pd+8ot66oN24ntu2m9q0mdmyltixlPvx6/rv6Pru5uOxkGBIMAAAAAAAAL9uSu61nvv2
+9adQJohLJs64q/jw7vjw7qdQJlU7FbOhkPbq5/bo5vXn5MeOdIY7B/37+vz6+fz6+Pz59/z4
+9vz39Pz28vv18fv07/vy7Pvw6/vv6OKzlGBIMAAAAAAAAMRzUe61nvv498F8Xtd2R4hLJuzm
+4/bu7MxoO4hLJlU7Feja1Pbq5/Xq5taVe41EFf37+/37+syfgMyfgMyfgMyfgMyfgMyfgMyf
+gPzz7vvy7Pvw6uC1mGBIMAAAAAAAAMx6VO+8qPv498WBaueUdNd2R4hLJuDQx9WAWdd2R4hL
+JpqIdvbs6Pbq59aVe41EFf38+/37+vHIqOS8oOXBpeW9ouCxkeK0lcyfgPz07/vy7fvx69+3
+mmBIMAAAAAAAAM99V++8qPz5+NCJcbF9Ztiagdd2R4hLJr9uSsWBarJiOFU7Feja1Pbs6Nia
+gY1EFf38/P38+/HIqNenht2wkOO3md6vkOK1l8yfgPz08Pv07vvx7N+4nWBIMAAAAAAAANOB
+X++8qPz6+daVe7F9Zui6p8WBatd2R4hLJuSpkcpzSIhLJpqIdvft6tiagZhLIP38/P38+/HI
+qOe/o+bBpuS8oNytjOG2l8yfgPz18fv07/vz7d25n2BIMAAAAAAAANmGZfLGtf38+9uiibF9
+ZvPk39uiieSpkYhLJuDQx926rbJiOFU7Ffbu7NuiiZhLIP38/P38+/HIqPLh1erWyPnz7uLA
+qOG/peC9o9y5o9y5o9y5o926oWBIMAAAAAAAAN6KaPLGtdaVe+SpkeSpkbF9Zv39/c2DZ41R
+L+DQx+SpkcWBarJiONiagduiiZ1SKP38/P38+/HIqPv39Pz7++nVx7yqm4p2Y4ZyXoBrV3pj
+TnJbRWpTPGBIMAAAAAAAAN6KaPLGtdaVe9CJcdCJccRzUd26rfz5+JZZOfv29ch2Ucl7XMl7
+XL9uSuejh6JZK/38/P38+/HIqP39/fny7fLh1b2rm+rQvejMueXIs+LCrN+8p2BIMCUlJAAA
+AAAAAOSPbvLGtf39/f39/f38+/38+/z7+vz6+fz5+Pv39vv29fr18/rz8vjy8OSpkaJZK/38
+/P38/PHIqPHIqPHIqPHIqL+snf3s4vnm2vLczuzSwWBIMCUlJBgYGAAAAAAAAOeUdPLGtf39
+/f39/f39/f39/P38+/z7+vz6+fz49/v49/v29fr18/rz8uSpkalcMf38/P38/P38+/z8+/37
++v37+cCunv3s4vnm2vPczmVONiUlJBgYGAAAAAAAAAAAAOeUdPLGtfLGtfLGtfLGtfLGtfLG
+te+8qO+8qO+8qO61nu61nuuvl+uvl+SpkalcMf38/P38/P38+/37+/37+v37+cKwof3s4vnm
+2mxVPiUlJBgYGAAAAAAAAAAAAAAAAOeUdOeUdOeUdOSPbuSPbt6KaNmGZdOBX9WAWc99V8h2
+UcRzUb9uSr9uSrdpQ6xgN/38/P38/P38/P38+/37+/37+sSyov3s4oFuWSUlJBgYGAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM+9r/39/f39/f39/f39/f39/f39/f39/f39/f39
+/f39/f38/P39/P38/P38/P37+/37+sazo5qEdCUlJBgYGAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAM+9r8+9r8+9r8+9r868rs27rc27rc26rMu5q8u5qsu3qsq3qcm2qMi1
+p8i1pse0pca0pce0pCUlJBgXFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAOM5Ag+ABgAARAEA0M8R4KGxGuEAAAAA
+AAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAACAAAAmQAAAAAAAAAAEAAAmwAAAAEA
+AAD+////AAAAAJMAAACaAAAA////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+///////////////////spcEAA2AJBAAA8BK/AAAAAAAAEAAAAAAABgAAaiYAAA4AYmpiastz
+y3MAAAAAAAAAAAAAAAAAAAAAAAAJBBYAKogAAKkZAQCpGQEA7B0AAAAAAAB9AAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAD//w8AAAAAAAAAAAD//w8AAAAAAAAAAAD//w8AAAAAAAAAAAAAAAAA
+AAAAAKQAAAAAACQGAAAAAAAAJAYAACQGAAAAAAAAJAYAAAAAAAAkBgAAAAAAACQGAAAAAAAA
+JAYAABQAAAAAAAAAAAAAADgGAAAAAAAAnDUAAAAAAACcNQAAAAAAAJw1AAA4AAAA1DUAABQA
+AADoNQAAbAEAADgGAAAAAAAA8VYAALYAAABgNwAAAAAAAGA3AAAoAAAAiDcAAAAAAACINwAA
+AAAAAIg3AAAAAAAAiDcAAAAAAACINwAAAAAAAIg3AAAAAAAAcFYAAAIAAAByVgAAAAAAAHJW
+AAAAAAAAclYAAAAAAAByVgAAAAAAAHJWAAAAAAAAclYAACQAAACnVwAAaAIAAA9aAACcAAAA
+llYAABUAAAAAAAAAAAAAAAAAAAAAAAAAJAYAAAAAAABrOAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AACINwAAAAAAAIg3AAAAAAAAazgAAAAAAABrOAAAAAAAAJZWAAAAAAAAAAAAAAAAAAAkBgAA
+AAAAACQGAAAAAAAAiDcAAAAAAAAAAAAAAAAAAIg3AAAAAAAAq1YAABYAAAAtOwAAAAAAAC07
+AAAAAAAALTsAAAAAAABrOAAA1gAAACQGAAAAAAAAiDcAAAAAAAAkBgAAAAAAAIg3AAAAAAAA
+cFYAAAAAAAAAAAAAAAAAAC07AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAazgAAAAAAABwVgAAAAAAAAAAAAAAAAAALTsAAAAAAAAtOwAA
+RgQAALRJAAAQAwAAJAYAAAAAAAAkBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbE0AAAAAAACINwAAAAAAAFQ3AAAMAAAA
+sFxZ1E5zygEAAAAAAAAAAJw1AAAAAAAAQTkAAPoAAADETAAAVAAAAAAAAAAAAAAAlE0AANwI
+AADBVgAAMAAAAPFWAAAAAAAAGE0AAFQAAACrWgAAAAAAADs6AACCAAAAq1oAAKgAAABsTQAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKtaAAAAAAAAAAAAAAAAAAAkBgAAAAAAAGxNAAAoAAAA
+iDcAACIAAACqNwAAGAAAAC07AAAAAAAAwjcAABQAAADWNwAAlQAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAiDcAAAAAAACINwAAAAAAAIg3AAAAAAAAllYAAAAAAACWVgAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvToAAHAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIg3AAAAAAAAiDcAAAAAAACINwAAAAAAAPFWAAAAAAAA
+azgAAAAAAABrOAAAAAAAAGs4AAAAAAAAazgAAAAAAAAAAAAAAAAAADgGAAAAAAAAOAYAAAAA
+AAA4BgAAZC8AAJw1AAAAAAAAOAYAAAAAAAA4BgAAAAAAADgGAAAAAAAAnDUAAAAAAAA4BgAA
+AAAAADgGAAAAAAAAOAYAAAAAAAAkBgAAAAAAACQGAAAAAAAAJAYAAAAAAAAkBgAAAAAAACQG
+AAAAAAAAJAYAAAAAAAD/////AAAAAAIADAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAEFjdGlvbnMgQXJpc2luZyBmcm9tIGEgTWVldGluZyBvZiB0aGUgQ2hpZWYg
+T2ZmaWNlciBHcm91cCBoZWxkIGF0IA0wOTMwIGhvdXJzIG9uIFdlZG5lc2RheSwgOCBTZXB0
+ZW1iZXIgMjAwNCBpbiBBQ0MoT1MpIG9mZmljZSANDVByZXNlbnQ6IC0gICBDaGllZiBDb25z
+dGFibGUsIERlcHV0eSBDaGllZiBDb25zdGFibGUsIEFzc2lzdGFudCBDaGllZiBPZmZpY2Vy
+IChTdXBwb3J0KSwgQXNzaXN0YW50IENoaWVmIENvbnN0YWJsZSAoT3BlcmF0aW9ucykgYW5k
+IENoaWVmIFN1cGVyaW50ZW5kZW50IFdoaXRlCQ0NSXRlbQcHQWN0aW9uBwcHT1BFTiBTRVNT
+SU9OBwcHBwcHBzEuB0Fwb2xvZ2llcwcHBwcHBwcHQXNzaXN0YW50IENoaWVmIENvbnN0YWJs
+ZSAoT3BlcmF0aW9ucyBTdXBwb3J0KS4HBwcHBwcHMi4HTWF0dGVycyBBcmlzaW5nIGZyb20g
+TWludXRlcyAHBwcHBwcHB0luIHJlZmVycmluZyB0byB0aGUgbWludXRlcyBvZiB0aGUgbWVl
+dGluZyBoZWxkIG9uIDEgU2VwdGVtYmVyIDIwMDQgY29uY2VybmluZyBJdGVtIDYgKFZ1bG5l
+cmFibGUgV2l0bmVzcyBPZmZpY2VycyksIGl0IHdhcyBhZ3JlZWQgdGhhdCB0byBwcmFjdGlj
+YWxseSBtb3ZlIGFuZCByZWxvY2F0ZSBvZmZpY2VycyB3aXRoIHRoZXNlIHNraWxscyB3b3Vs
+ZCBiZSBpbXByYWN0aWNhbCBhbmQgdGhlIHByb2JsZW0gY29uY2VybmVkIHRoZSBmYWlyIGRp
+c3RyaWJ1dGlvbiBvZiB0aGUgVldPIHNraWxscyBhY3Jvc3MgdGhlIEZvcmNlIGFyZWEuICBB
+Q0MoT1MpIHRvIHJldmlzaXQgdGhlIHJlY29tbWVuZGF0aW9ucyBhbmQgZGV2ZWxvcCBhIG1v
+cmUgdmlhYmxlIHNvbHV0aW9uIHRoYXQgZGlkIG5vdCBpbnZvbHZlIHRoZSBwaHlzaWNhbCBt
+b3ZlbWVudCBvZiBpbmRpdmlkdWFsIHN0YWZmIGJ1dCB0aGUgcmVkaXN0cmlidXRpb24gb2Yg
+dGhlIHNraWxsLgcNDQ0NDQ1BQ0MoT1MpIAcHBwcHBzMuB0NhbGwgSGFuZGxpbmcgVXBkYXRl
+IAcHBwcHBwcHQ2hpZWYgT2ZmaWNlcnMgd2VyZSBhZHZpc2VkIHRoYXQgYSBDb25zdWx0YW5j
+eSBSZXBvcnQgKFdvbGZlbmRlbikgaGFkIGJlZW4gcmVjZWl2ZWQgY29uY2VybmluZyByZXNv
+dXJjaW5nIGxldmVscyBpbiB0aGUgY2FsbCBoYW5kbGluZyBmdW5jdGlvbi4gIERpZmZpY3Vs
+dGllcyBoYWQgYmVlbiBleHBlcmllbmNlZCBpbiBib3RoIENvbW1hbmQgQ2VudHJlcyBkdXJp
+bmcgdGhlIHByZXZpb3VzIHdlZWtlbmQsIHdpdGggdGhlIHZvbHVtZSBhbmQgZGVtYW5kIG9m
+ICAgICA5OTkgY2FsbHMsIHdpdGggbmVpZ2hib3VyaW5nIGZvcmNlcyBiZWluZyBhc2tlZCB0
+byBhc3Npc3Qgd2l0aCBjYWxsIGhhbmRsaW5nLiAgQ2hpZWYgT2ZmaWNlcnMgdG8gY29uc2lk
+ZXIgbW9yZSBkZXRhaWxlZCBjYWxsIGhhbmRsaW5nIHVwZGF0ZSBmcm9tIEFDQyhPUykgYXQg
+bmV4dCBtZWV0aW5nLiAgQUNDKE8pIHRvIG1vbml0b3IgQ29tbWFuZCBDZW50cmUgZGVtYW5k
+IGxldmVscyBkdXJpbmcgdGhlIG5leHQgc2V2ZW4gZGF5cy4HDQ0NDQ0NQUNDKE9TKQ0NDUFD
+QyhPKQcHBwcHBzQuB1Zpc2l0IHRvIEF2b24gYW5kIFNvbWVyc2V0IENvbnN0YWJ1bGFyeQcH
+BwcHBwcHVGhlIENoaWVmIENvbnN0YWJsZSBwcmVzZW50ZWQgdG8gQ09HIGEgbWF0cml4IHN1
+bW1hcmlzaW5nIHRoZSBnb29kIHByYWN0aWNlIGxlYXJudCBmcm9tIHRoZSByZWNlbnQgdmlz
+aXQgdG8gQXZvbiAmIFNvbWVyc2V0IENvbnN0YWJ1bGFyeS4gIFRoZSByZXBvcnQgY29udGFp
+bmVkIGEgbnVtYmVyIG9mIHJlY29tbWVuZGF0aW9ucyBmb3IgaW1wcm92ZW1lbnQuIFRoZXNl
+IHdlcmUgYXNzaWduZWQgaW5kaXZpZHVhbGx5IHRvIENoaWVmIE9mZmljZXJzLgcHBw0NDQ0N
+SXRlbQcHQWN0aW9uBwcHBwcHNS4HQUNQTyBMZWFkIGZvciBHdW4gUmVsYXRlZCBDcmltZQcH
+BwcHBwcHQ2hpZWYgT2ZmaWNlcnMgY29uc2lkZXJlZCBjb3JyZXNwb25kZW5jZSByZWNlaXZl
+ZCBmcm9tIHRoZSBDaGFpciBvZiB0aGUgQUNQTyBDcmltaW5hbCBVc2Ugb2YgRmlyZWFybXMg
+R3JvdXAgaW52aXRpbmcgdGhlIEZvcmNlIHRvIG5vbWluYXRlIGFuIEFDUE8gbGVhZCBmb3Ig
+R3VuIENyaW1lIFByZXZlbnRpb24gYW5kIENvbW11bml0eSBQcm90ZWN0aW9uLiAgSXQgd2Fz
+IGFncmVlZCB0aGF0IEFDQyhPUykgd291bGQgYmUgdGhlIGxlYWQgZm9yIHRoZSBGb3JjZSBh
+bmQgd291bGQgd3JpdGUgYmFjayB0byBDb21tYW5kZXIgRGljayBmcm9tIHRoZSBDb21taXR0
+ZWUuBw0NDUFDQyhPUykHBwcHBwc2LgdQb2xpY2UgUmVmb3JtIE1pbmlzdGVyaWFsIFN0ZWVy
+aW5nIEdyb3VwICAHBwcHBwcHB0NoaWVmIE9mZmljZXJzIHJlY2VpdmVkIGEgYnJpZWZpbmcg
+dXBkYXRlIGNvbmNlcm5pbmcNIHRoZSBkZXZlbG9wbWVudCBvZiB0aGUgbmV4dCBzdGFnZSBv
+ZiBQb2xpY2UgUmVmb3JtLiBDaGllZiBPZmZpY2VycyBhcmUgZHVlIHRvIGF0dGVuZCBhIGJy
+aWVmaW5nIGZyb20gdGhlIEhvbWUgU2VjcmV0YXJ5IGFuZCBvdGhlcnMgb24gVGh1cnNkYXks
+IDkgU2VwdGVtYmVyIDIwMDQsIGluIExvbmRvbiBhbmQsIGZvbGxvd2luZyB0aGF0IGJyaWVm
+aW5nLCBtb3JlIGRldGFpbGVkIGRpc2N1c3Npb25zIGNvbmNlcm5pbmcgdGhlIGltcGxpY2F0
+aW9ucyBvZiByZWZvcm0gd291bGQgdGFrZSBwbGFjZSBhdCBmdXR1cmUgQ09HLgcHBwcHBwc3
+LgdCZXN0IFZhbHVlIFJldmlldyBDaGlsZCBQcm90ZWN0aW9uBwcHBwcHBwdUaGUgRGVwdXR5
+IENoaWVmIENvbnN0YWJsZSBpbmZvcm1lZCBDaGllZiBPZmZpY2VyIGNvbGxlYWd1ZXMgdGhh
+dCBmb2xsb3dpbmcgYSByZWNlbnQgSE1JQyBCZXN0IFZhbHVlIGluc3BlY3Rpb24gb2YgdGhl
+IENoaWxkIFByb3RlY3Rpb24gZnVuY3Rpb24gd2l0aCBIdW1iZXJzaWRlLCB0aGUgY3VycmVu
+dCBzZXJ2aWNlIGhhZCBiZWVuIGdyYWRlZCBhcyCTZmFpcpQgd2l0aCB0aGVyZSBiZWluZyBh
+IHByb21pc2luZyBvdXRsb29rIGZvciBmdXR1cmUgaW1wcm92ZW1lbnRzIGluIHRoZSBzZXJ2
+aWNlLiAgVGhpcyB3YXMgaW1wb3J0YW50IGFuZCB2YWx1YWJsZSBmZWVkYmFjayBmcm9tIEhN
+SUMgYW5kIENoaWVmIE9mZmljZXJzIGNvbmZpcm1lZCB0aGVpciBjb21taXRtZW50IHRvIGRl
+bGl2ZXJpbmcgdGhlIHJlY29tbWVuZGF0aW9ucyBvZiB0aGlzIHZlcnkgaW1wb3J0YW50IEJl
+c3QgVmFsdWUgUmV2aWV3LiAHBwcHBwcHOC4HQmVzdCBWYWx1ZSBSZXZpZXcgQ2hpbGQgUHJv
+dGVjdGlvbgcHBwcHBwcHBwcHB0NoaWVmIE9mZmljZXJzIGNvbnNpZGVyZWQgYSBzcGVjaWZp
+YyByZXZpZXcgcmVjb21tZW5kYXRpb24gY29uY2VybmluZyB0aGUgbWFubmVyIGluIHdoaWNo
+IHN0YWZmIGluIHRoZSBDaGlsZCBQcm90ZWN0aW9uIGZ1bmN0aW9uIGFyZSBzZWxlY3RlZCwg
+dHJhaW5lZCBhbmQgZGlzdHJpYnV0ZWQgYW5kIGFsc28gaG93IHRoZSB3ZWxmYXJlIGFycmFu
+Z2VtZW50cyBvZiBzdWNoIHN0YWZmIHdlcmUgYWRkcmVzc2VkIGluIGEgc3RydWN0dXJlZCBt
+YW5uZXIuICBBIHJlcG9ydCBvbiB0aGUgc3ViamVjdCBieSBIZWFkIG9mIENyaW1lIE1hbmFn
+ZW1lbnQgQnJhbmNoIG1hZGUgYSBudW1iZXIgb2YgcmVjb21tZW5kYXRpb25zIGNvbmNlcm5p
+bmcgdGhlIGN1cnJlbnQgdGVudXJlIHBvbGljeSB3aXRoaW4gdGhlIEZhbWlseSBQcm90ZWN0
+aW9uIFRlYW0uICBJbiBjb25zaWRlcmluZyB0aGUgcHJpbmNpcGFsIHJlY29tbWVuZGF0aW9u
+IHRoYXQgdGVudXJlIHNob3VsZCBjZWFzZSwgQ2hpZWYgT2ZmaWNlcnMgY29uc2lkZXJlZCB0
+aGF0IHRoZSByZW1vdmFsIG9mIHRlbnVyZSBmcm9tIHNwZWNpYWxpc3QgZGVwYXJ0bWVudHMg
+YWNyb3NzIHRoZSBmb3JjZSBvbiBhIHBpZWNlbWVhbCwgb25lIGJ5IG9uZSBiYXNpcywgd2Fz
+IG5vdCB0aGUgYmVzdCB3YXkgdG8gcHJvY2VlZCBhbmQgdGhhdCB0aGlzIHJlcXVlc3QgYmUg
+cmVtaXR0ZWQgdGhyb3VnaCBBQ08oUykgdG8gUGVyc29ubmVsIEJyYW5jaCB3aG8gYXJlIGN1
+cnJlbnRseSB1bmRlcnRha2luZyBhIHJldmlldyBvZiB0ZW51cmUgYWNyb3NzIHRoZSBGb3Jj
+ZSBhcyBhIHdob2xlLgcNDQ0NDQ0NDQ0NDQ0NDUFDTyhTKQ0HBwcHBwc5LgdQcm9tYXQgUHJv
+amVjdCBSZXZpZXcgRG9jdW1lbnQHBwcHBwcHB0NoaWVmIE9mZmljZXJzIHJlY2VpdmVkIGEg
+cG9zdCBwcm9qZWN0IHJldmlldyBkb2N1bWVudCBwcmVwYXJlZCBieSBDaGllZiBJbnNwZWN0
+b3IgRWR3YXJkcyBvZiB0aGUgUHJvbWF0IElkZW50aWZpY2F0aW9uIFN5c3RlbS4gQ2hpZWYg
+T2ZmaWNlcnMgcGxhY2VkIG9uIHJlY29yZCB0aGVpciB0aGFua3MgdG8gQ2hpZWYgSW5zcGVj
+dG9yIEVkd2FyZHMgZm9yIHVuZGVydGFraW5nIHRoZSB3b3JrIGFuZCBub3RlZCB0aGUgdmVy
+eSBmYXZvdXJhYmxlIGNvbW1lbnRzIHdpdGggcmVnYXJkIHRvIHRoZSBpbXBsZW1lbnRhdGlv
+biBhbmQgcHJhY3RpY2FsIG9wZXJhdGlvbmFsIHVzZSBvZiBQcm9tYXQuICBJdCB3YXMgYWdy
+ZWVkIHRoYXQgcG9zdCBwcm9qZWN0IHJldmlld3Mgc2hvdWxkIGJlIG1vcmUgcm9idXN0bHkg
+aW5jbHVkZWQgaW4gdGhlIHByb2plY3QgbWFuYWdlbWVudCBzeXN0ZW0gb2YgdGhlIEZvcmNl
+IGFzIGEgc3RhbmRhcmQgYWN0aXZpdHkuIERDQywgYXMgQ2hhaXIgb2YgRm9yY2UgUHJvamVj
+dCBCb2FyZCB0byBpbXBsZW1lbnQuBw0NDQ0NDQ0NDURDQyAHBwcHBwcxMC4HQ29tbXVuaXR5
+IFN1cHBvcnQgT2ZmaWNlcnMHBwcHBwcHB0NoaWVmIE9mZmljZXJzIGNvbnNpZGVyZWQgYSBw
+YXBlciBwcmVwYXJlZCBieSBIZWFkIG9mIFN1cHBvcnQgU2VydmljZXMgaW4gcmVzcG9uc2Ug
+dG8gdGhlIEdvdmVybm1lbnSScyByZWNlbnQgYW5ub3VuY2VtZW50IG9mIHRoZSBuZXh0IHJv
+dW5kIG9mIGZ1bmRpbmcgZm9yIENvbW11bml0eSBTdXBwb3J0IE9mZmljZXJzLiBGb2xsb3dp
+bmcgZGlzY3Vzc2lvbiwgaXQgd2FzIGFncmVlZCB0aGF0IHRoZSBGb3JjZSB3b3VsZCBzdWJt
+aXQgYW4gb3V0bGluZSBhcHBsaWNhdGlvbiBmb3IgMzYgQ1NPcyB0byBiZSBlbXBsb3llZCBk
+dXJpbmcgMjAwNSB0byAyMDA3LCBidXQgb25seSBhZnRlciBkaXNjdXNzaW9uIHdpdGggdGhl
+IENoYWlyIG9mIHRoZSBQb2xpY2UgQXV0aG9yaXR5LiAgQ2hpZWYgQ29uc3RhYmxlIHRvIGRp
+c2N1c3Mgd2l0aCBDaGFpciBvZiB0aGUgQXV0aG9yaXR5LiBBQ08oUykgdG8gcHJlcGFyZSBv
+dXRsaW5lIGFwcGxpY2F0aW9uLgcNDQ0NDQ0NQ0MNQUNPKFMpBwcHBwcHMTEuB1RyYWluaW5n
+IGFuZCBTdXBlcnZpc2lvbiBCb2FyZAcHBwcHBwcHQUNPKFMpIHJlbWluZGVkIENoaWVmIE9m
+ZmljZXIgY29sbGVhZ3VlcyAgdGhhdCwgYXMgQ2hhaXIgb2YgdGhlIFRyYWluaW5nIGFuZCBT
+dXBlcnZpc2lvbiBCb2FyZCwgaXQgd2FzIGltcG9ydGFudCB0aGF0IHRoZSBidXNpbmVzcyB1
+c2VyIHJlcXVpcmVtZW50IG9mIENoaWVmIE9mZmljZXJzLCBEaXZpc2lvbnMgYW5kIEJyYW5j
+aGVzIHdhcyBwcm9wZXJseSByZXByZXNlbnRlZCBhdCB0aGlzIEJvYXJkIGFuZCBoZSBpbnZp
+dGVkIGNvbGxlYWd1ZXMgdG8gaW5jbHVkZSBmdXR1cmUgbWVldGluZyBkYXRlcyB3aXRoaW4g
+dGhlaXIgZGlhcnkgYW5kIHRvIGF0dGVuZCB3aGVyZSBwb3NzaWJsZS4HDQ0NQ2hpZWYgT2Zm
+aWNlcnMgdG8gZGlhcnkgbWVldGluZyBkYXRlcy4HBwcHBwcxMi4HTWVtYmVyc2hpcCBvZiB0
+aGUgQnJpdGlzaCBOYXRpb25hbCBQYXJ0eQcHBwcHBwcHQ2hpZWYgT2ZmaWNlcnMgcmVjZWl2
+ZWQgYSByZXBvcnQgZnJvbSBIZWFkIG9mIFBlcnNvbm5lbCBwcm92aWRpbmcgYW4gb3ZlcnZp
+ZXcgb2YgbmF0aW9uYWwgZGV2ZWxvcG1lbnRzIHdpdGggcmVnYXJkIHRvIG1lbWJlcnNoaXAg
+b2YgdGhlIEJyaXRpc2ggTmF0aW9uYWwgUGFydHkuICBJdCB3YXMgbm90ZWQgdGhhdCBtZW1i
+ZXJzaGlwIG9mIHRoZSBCTlAgYnkgcG9saWNlIG9mZmljZXJzIGlzIHByb2hpYml0ZWQgYW5k
+IG5vdyB3aWRlbHkgZXN0YWJsaXNoZWQgYXMgc3VjaCBhY3Jvc3MgYWxsIGZvcmNlcy4gIENo
+aWVmIE9mZmljZXJzIGRpc2N1c3NlZCBpc3N1ZXMgc3Vycm91bmRpbmcgdGhlIG1lbWJlcnNo
+aXAgb2Ygc3VwcG9ydCBzdGFmZiBhbmQgaXQgd2FzIGFncmVlZCB0aGF0IGNvbnN1bHRhdGlv
+biB3b3VsZCB0YWtlIHBsYWNlIHdpdGggU3RhZmYgQXNzb2NpYXRpb25zIHRvIGRldGVybWlu
+ZSB0aGVpciB2aWV3LiBDaGllZiBDb25zdGFibGUgdG8gd3JpdGUgdG8gU3RhZmYgQXNzb2Np
+YXRpb25zLgcNDQ0NDQ0NDUNDDQcHBwcNBwdJdGVtBwdBY3Rpb24HBwcHBwcxMy4HQ2hpZWYg
+T2ZmaWNlciBWaXNpdHMHBwcHBwcHB0FDQyhPKSBicmllZmVkIENoaWVmIE9mZmljZXIgY29s
+bGVhZ3VlcyBjb25jZXJuaW5nIHRoZSBmdW5lcmFsIG9uIFR1ZXNkYXkgb2YgdGhpcyB3ZWVr
+IG9mIFNndCBEYXZpZCBSaWNrYWxscyBmcm9tIEEgRGl2aXNpb24uICBIZSBwbGFjZWQgb24g
+cmVjb3JkIGhpcyB0aGFua3MgdG8gdGhlIERpdmlzaW9uIGZvciB0aGUgbW9zdCBleGNlbGxl
+bnQgYXJyYW5nZW1lbnRzIHRoYXQgaGFkIGJlZW4gcHV0IGluIHBsYWNlIGZvciBTZ3QgUmlj
+a2FsbHOSIGZ1bmVyYWwgIHdoaWNoIGhhZCBiZWVuIGEgY3JlZGl0IHRvIHRoZSBEaXZpc2lv
+biBhbmQgdG8gdGhlIEZvcmNlLgcHBwcHBwcxNC4HUHJvYmF0aW9uZXIgVHJhaW5pbmcHBwcH
+BwcHB0FDTyhTKSBicmllZmVkIGNvbGxlYWd1ZXMgY29uY2VybmluZyB0aGUgZnVydGhlciBk
+ZXZlbG9wbWVudCBvZiBwcm9iYXRpb25lciB0cmFpbmluZyBhY3Jvc3MgdGhlIHJlZ2lvbi4g
+IEl0IHdhcyBwcm9wb3NlZCB0aGF0IEh1bWJlcnNpZGUgd291bGQgbG9vayBtb3JlIGNsb3Nl
+bHkgYXQgbG9jYWwgYXJyYW5nZW1lbnRzIHdpdGggV2VzdCBZb3Jrc2hpcmUgYW5kIG90aGVy
+IHJlZ2lvbmFsIHBvbGljZSBmb3JjZXMgYW5kIGluIHRoaXMgcmVzcGVjdCBpdCB3YXMgcHJv
+cG9zZWQgdGhhdCBvbmUgdHJhaW5lciBmcm9tIEh1bWJlcnNpZGUgYmUgc2Vjb25kZWQgdG8g
+dGhlIHBpbG90IFdlc3QgWW9ya3NoaXJlIFBvbGljZSBwcm9ncmFtbWUuICBUaGlzIHdhcyBh
+Z3JlZWQgYnkgQ2hpZWYgT2ZmaWNlcnMuIEFDTyhTKSB0byBwcm9ncmVzcy4HDQ0NDQ0NDUFD
+TyhTKQcHBwcHBwdDTE9TRUQgU0VTU0lPTgcHBwcHBwcxNS4HVmV0dGluZyBQb2xpY3kgZm9y
+IHRoZSBQb2xpY2UgQ29tbXVuaXR5BwcHBwcHBwdDaGllZiBPZmZpY2VycyByZWNlaXZlZCBh
+IHJlcG9ydCBmcm9tIHRoZSBJbmZvcm1hdGlvbiBTZWN1cml0eSBPZmZpY2VycyB1cGRhdGlu
+ZyB0aGVtIGNvbmNlcm5pbmcgZGV2ZWxvcG1lbnRzIGluIHRoZSBBQ1BPIE5hdGlvbmFsIFZl
+dHRpbmcgUG9saWN5IGZvciB0aGUgUG9saWNlIENvbW11bml0eS4gIEl0IHdhcyBub3RlZCB0
+aGF0IHRoZXJlIHdpbGwgYmUgYSBmdXR1cmUgcmVxdWlyZW1lbnQgdG8gdW5kZXJ0YWtlIHdp
+ZGVzcHJlYWQgdmV0dGluZyBvZiBhbGwgZW1wbG95ZWVzIG9mIEh1bWJlcnNpZGUgUG9saWNl
+IGFzIHdlbGwgYXMgY2hhbmdlcyB0byBuZXcgZW1wbG95ZWVzLiAgRm9sbG93aW5nIGRpc2N1
+c3Npb24sIGl0IHdhcyBhZ3JlZWQgdGhhdCB0aGlzIHBpZWNlIG9mIHdvcmsgbmVlZGVkIHRv
+IGJlIHB1dCBvbiBhIHByb2plY3QgYmFzZWQgZm9vdGluZyBhbmQgdGhlIERDQyB3YXMgYXNr
+ZWQgdG8gZXN0YWJsaXNoIGEgcHJvamVjdCBncm91cCB0byBzY29wZSB0aGUgd29yayBpbnZv
+bHZlZC4gIERDQyB0byBjb21taXNzaW9uIHByb2plY3QgZ3JvdXAuBw0NDQ0NDQ0NDQ1EQ0MH
+Bw0DDQ0EDQ0DDQ0EDQ0NDQ0NE1BBR0UgIBQzFQ0NDRNQQUdFICAUMRUNQ09HIE1pbnMgMDgu
+MDkuMDQNRmlsZSBjbGFzc2lmaWNhdGlvbjogTk9UIFBST1RFQ1RJVkVMWSBNQVJLRUQgLSBO
+TyBERVNDUklQVE9SDQ0NDQ0NDQ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAIAI
+AACBCAAALwkAADAJAABACQAAQQkAAEIJAABHCQAAUQkAAFIJAABTCQAAVAkAAFUJAACHCQAA
+iAkAAIkJAACKCQAAiwkAAIwJAACRCQAArwkAALAJAACxCQAApgsAAKcLAACoCwAAqQsAAKoL
+AACrCwAArgsAAMQLAADHCwAAyAsAAMkLAADKCwAA5Q0AAOYNAADrDQAAEw4AABQOAAAVDgAA
+Fg4AABcOAAAbDwAAJw8AACgPAAAxDwAAMg8AADMPAAA3DwAAXA8AAF0PAABeDwAAnxAAAKAQ
+AACrEAAArRAAAK4QAACwEAAAsxAAAN4QAADjEAAA5BAAAEcSAABIEgAASRIAAEoSAABOEgAA
+cRIAAHYSAAB3EgAARxQAAEgUAABJFAAAShQAAE0UAABwFAAAcxQAAHQUAAB1FAAAdhQAAHkU
+AAB6FAAAuBcAALkXAAC+FwAA3RcAAN4XAADfFwAA4BcAAOEXAAAIGgAACRoAABEaAAAsGgAA
+LRoAAC4aAAAvGgAA/Pf89/z3/Pf89/z3/Pf89/z3/Pf89/z3/Pf89/z3/Pf89/z3/Pf89/z3
+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
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAa2QzAwAAFiQBFyQBSWYBAAAAApZsAAjWRgADlP/h
+A7Ab3CMABk0EAAAAAAAAAAAAAAAAAAAAAAAGzxcAAAAAAAAAAAAAAAAAAAAAAAYsCAAAAAAA
+AAAAAAAAAAAAAAAU9gEAABrWDAAAAP8AAAD/AAAA/xvWDAAAAP8AAAD/AAAA/xzWDAAAAP8A
+AAD/AAAA/x3WDAAAAP8AAAD/AAAA/zTWBgABCgNsAGH2AwAACRAAAyQBFiQBSWYBAAAAYSQB
+BhAAFiQBSWYBAAAAAAe0CQAAtQkAALYJAACXCwAAmAsAAJkLAACaCwAAmwsAAJwLAACdCwAA
+pgsAAKYAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAlwAAAAAAAAAAAAAAAKAAAAAAAAAAAAAA
+AACgAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAA
+oAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkQAAMkAxYk
+AUlmAQAAAGEkAwYQABYkAUlmAQAAAABYAABrZKgDAAAWJAEXJAFJZgEAAAAClmwACNZGAAOU
+/+EDsBvcIwAGTQQAAAAAAAAAAAAAAAAAAAAAAAbPFwAAAAAAAAAAAAAAAAAAAAAABiwIAAAA
+AAAAAAAAAAAAAAAAABT2AQAAGtYMAAAA/wAAAP8AAAD/G9YMAAAA/wAAAP8AAAD/HNYMAAAA
+/wAAAP8AAAD/HdYMAAAA/wAAAP8AAAD/NNYGAAEKA2wAYfYDAAAACqYLAACnCwAAqAsAAKkL
+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
++QAAAAAAAAAAAAAAAIUAAAAAAAAAAAAAAAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAJEAADJAEWJAFJZgEAAABhJAEADhAAAyQDDcYIAAIHAvsNwEAWJAFJZgEAAABhJAMAWAAA
+a2QdDwAAFiQBFyQBSWYBAAAAApZsAAjWRgADlP/hA7Ab3CMABk0EAAAAAAAAAAAAAAAAAAAA
+AAAGzxcAAAAAAAAAAAAAAAAAAAAAAAYsCAAAAAAAAAAAAAAAAAAAAAAU9gEAABrWDAAAAP8A
+AAD/AAAA/xvWDAAAAP8AAAD/AAAA/xzWDAAAAP8AAAD/AAAA/x3WDAAAAP8AAAD/AAAA/zTW
+BgABCgNsAGH2AwAAAAsQAA3GCAACBwL7DcBAFiQBSWYBAAAABhAAFiQBSWYBAAAAAAd5FAAA
+ehQAAHsUAACgFwAAoRcAAKIXAACjFwAApBcAAKUXAACmFwAApxcAAKgXAACpFwAAqhcAAKsX
+AACsFwAArRcAAKYAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAkQAAAAAAAAAAAAAAAKAAAAAA
+AAAAAAAAAACgAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAACgAAAAAAAA
+AAAAAAAAoAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAoAAAAAAAAAAA
+AAAAAKAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAOEAADJAMNxggAAgcC+w3AQBYkAUlmAQAAAGEkAwYQABYkAUlm
+AQAAAABYAABrZJIPAAAWJAEXJAFJZgEAAAAClmwACNZGAAOU/+EDsBvcIwAGTQQAAAAAAAAA
+AAAAAAAAAAAAAAbPFwAAAAAAAAAAAAAAAAAAAAAABiwIAAAAAAAAAAAAAAAAAAAAABT2AQAA
+GtYMAAAA/wAAAP8AAAD/G9YMAAAA/wAAAP8AAAD/HNYMAAAA/wAAAP8AAAD/HdYMAAAA/wAA
+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
+9SUAAPYlAAD4JQAA+SUAAPolAAD7JQAA/CUAAP0lAAADJgAABCYAAAUmAAAGJgAAByYAAAkm
+AAAKJgAAECYAABEmAAASJgAAEyYAABQmAAAlJgAAYiYAAGMmAABkJgAAZSYAAGYmAABnJgAA
+aSYAAGomAAD89/z3/Pf89/z3/Pf89/z3/Pf89/z3/Pf89/z3/Pf89/z3/Pf87+vv6+/r7+vn
+6+fr3dfdzN3X/N3X3cHd17u1u/zn6+fr/AAAAAoWaNZFygBDShIAAAoWaKo+OgBDShIAABUW
+aNZFygAwShYAbUgABG5IAAR1CAEVFmiqPjoAMEoWAG1IAARuSAAEdQgBChZoqj46ADBKFgAA
+EwNqAAAAABZoqj46ADBKFgBVCAEGFmjWRcoAAAYWaEI/YwAADwNqAAAAABZoQj9jAFUIAQkW
+aKo+OgA1CIEGFmiqPjoARxQcAAAVHAAAFhwAABccAAAYHAAAGRwAABocAAAbHAAAHhwAACUc
+AAAmHAAAJxwAACgcAAApHAAA+QAAAAAAAAAAAAAAAPkAAAAAAAAAAAAAAAD5AAAAAAAAAAAA
+AAAA+QAAAAAAAAAAAAAAAPkAAAAAAAAAAAAAAAD5AAAAAAAAAAAAAAAA+QAAAAAAAAAAAAAA
+APkAAAAAAAAAAAAAAAD5AAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAPkAAAAAAAAAAAAAAAD5
+AAAAAAAAAAAAAAAA+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAFgAAGtkrxMAABYkARckAUlmAQAAAAKWbAAI1kYAA5T/4QOwG9wjAAZN
+BAAAAAAAAAAAAAAAAAAAAAAABs8XAAAAAAAAAAAAAAAAAAAAAAAGLAgAAAAAAAAAAAAAAAAA
+AAAAFPYBAAAa1gwAAAD/AAAA/wAAAP8b1gwAAAD/AAAA/wAAAP8c1gwAAAD/AAAA/wAAAP8d
+1gwAAAD/AAAA/wAAAP801gYAAQoDbABh9gMAAAYQABYkAUlmAQAAAAANKRwAACocAAAuHAAA
+TRwAAE4cAABPHAAAUBwAAFEcAABSHAAApgAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAACgAAAA
+AAAAAAAAAAAAoAAAAAAAAAAAAAAAAEcAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAoAAAAAAA
+AAAAAAAAAKAAAAAAAAAAAAAAAAAAAABYAABrZJkUAAAWJAEXJAFJZgEAAAAClmwACNZGAAOU
+/+EDsBvcIwAGTQQAAAAAAAAAAAAAAAAAAAAAAAbPFwAAAAAAAAAAAAAAAAAAAAAABiwIAAAA
+AAAAAAAAAAAAAAAAABT2AQAAGtYMAAAA/wAAAP8AAAD/G9YMAAAA/wAAAP8AAAD/HNYMAAAA
+/wAAAP8AAAD/HdYMAAAA/wAAAP8AAAD/NNYGAAEKA2wAYfYDAAAGEAAWJAFJZgEAAAAAWAAA
+a2QkFAAAFiQBFyQBSWYBAAAAApZsAAjWRgADlP/hA7Ab3CMABk0EAAAAAAAAAAAAAAAAAAAA
+AAAGzxcAAAAAAAAAAAAAAAAAAAAAAAYsCAAAAAAAAAAAAAAAAAAAAAAU9gEAABrWDAAAAP8A
+AAD/AAAA/xvWDAAAAP8AAAD/AAAA/xzWDAAAAP8AAAD/AAAA/x3WDAAAAP8AAAD/AAAA/zTW
+BgABCgNsAGH2AwAAAAhSHAAAUxwAAFQcAACkHQAApR0AAKYdAACnHQAAzh0AAKYAAAAAAAAA
+AAAAAACgAAAAAAAAAAAAAAAAlwAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAACgAAAAAAAAAAAA
+AAAAoAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkQAAMkAxYk
+AUlmAQAAAGEkAwYQABYkAUlmAQAAAABYAABrZA4VAAAWJAEXJAFJZgEAAAAClmwACNZGAAOU
+/+EDsBvcIwAGTQQAAAAAAAAAAAAAAAAAAAAAAAbPFwAAAAAAAAAAAAAAAAAAAAAABiwIAAAA
+AAAAAAAAAAAAAAAAABT2AQAAGtYMAAAA/wAAAP8AAAD/G9YMAAAA/wAAAP8AAAD/HNYMAAAA
+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
+AAAAAAAA8gAAAAAAAAAAAAAAAOwAAAAAAAAAAAAAAADsAAAAAAAAAAAAAAAA/QAAAAAAAAAA
+AAAAAPsAAAAAAAAAAAAAAAD9AAAAAAAAAAAAAAAA6gAAAAAAAAAAAAAAAP0AAAAAAAAAAAAA
+AAD9AAAAAAAAAAAAAAAA/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAABFQAABRUADoRoAV2EaAEACBUAGIT4/xmEAQAbJmAjJAIAARQAAAEAAAAW
+KAAfsIIuILDGQSGwBQcisCcHI5BuBCSQ+QAlsAAAF7DQAhiw0AIMkNACAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUA
+AQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YA
+AAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAh
+dgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZs
+ABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlm
+AQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgID
+LAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABz
+ABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQj
+dgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg0
+1gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMD
+LAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88X
+NdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQEC
+A88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNN
+BDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUA
+AQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YA
+AAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAh
+dgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZs
+ABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlm
+AQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgID
+LAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABz
+ABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQj
+dgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg0
+1gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMD
+LAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88X
+NdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQEC
+A88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNN
+BDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUA
+AQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YA
+AAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAh
+dgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZs
+ABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlm
+AQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgID
+LAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABz
+ABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQj
+dgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg0
+1gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMD
+LAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88X
+NdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQEC
+A88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNN
+BDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUA
+AQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YA
+AAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAh
+dgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZs
+ABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlm
+AQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgID
+LAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABz
+ABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQj
+dgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg0
+1gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMD
+LAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNNBDXWBQECA88X
+NdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUAAQNNBDXWBQEC
+A88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YAAAA11gUAAQNN
+BDXWBQECA88XNdYFAgMDLAg01gYAAQUAAABzABYkARckAUlmAQAAAAGWAAAhdgADaAE11gUA
+AQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZsABT2AQAAF/YA
+AAA11gUAAQNNBDXWBQECA88XNdYFAgMDLAg01gYAAQUAAAB3ABYkARckAUlmAQAAAAGWAAAh
+dgADaAE11gUAAQNNBDXWBQECA88XNdYFAgMDLAgjdgABTQQjdgECzxcjdgIDLAg6VgsAApZs
+AAeUIAEU9gEAABf2AAAANdYFAAEDTQQ11gUBAgPPFzXWBQIDAywINNYGAAEFAAAAcwAWJAEX
+JAFJZgEAAAABlgAAIXYAA2gBNdYFAAEDTQQ11gUBAgPPFzXWBQIDAywII3YAAU0EI3YBAs8X
+I3YCAywIOlYLAAKWbAAU9gEAABf2AAAANdYFAAEDTQQ11gUBAgPPFzXWBQIDAywINNYGAAEF
+AAAAcwAWJAEXJAFJZgEAAAABlgAAIXYAA2gBNdYFAAEDTQQ11gUBAgPPFzXWBQIDAywII3YA
+AU0EI3YBAs8XI3YCAywIOlYLAAKWbAAU9gEAABf2AAAANdYFAAEDTQQ11gUBAgPPFzXWBQID
+AywINNYGAAEFAAAAcwAWJAEXJAFJZgEAAAABlgAAIXYAA2gBNdYFAAEDTQQ11gUBAgPPFzXW
+BQIDAywII3YAAU0EI3YBAs8XI3YCAywIOlYLAAKWbAAU9gEAABf2AAAANdYFAAEDTQQ11gUB
+AgPPFzXWBQIDAywINNYGAAEFAAAAcwAWJAEXJAFJZgEAAAABlgAAIXYAA2gBNdYFAAEDTQQ1
+1gUBAgPPFzXWBQIDAywII3YAAU0EI3YBAs8XI3YCAywIOlYLAAKWbAAU9gEAABf2AAAANdYF
+AAEDTQQ11gUBAgPPFzXWBQIDAywINNYGAAEFAAAAcwAWJAEXJAFJZgEAAAABlgAAIXYAA2gB
+NdYFAAEDTQQ11gUBAgPPFzXWBQIDAywII3YAAU0EI3YBAs8XI3YCAywIOlYLAAKWbAAU9gEA
+ABf2AAAANdYFAAEDTQQ11gUBAgPPFzXWBQIDAywINNYGAAEFAAAAdwAWJAEXJAFJZgEAAAAB
+lgAAIXYAA2gBNdYFAAEDTQQ11gUBAgPPFzXWBQIDAywII3YAAU0EI3YBAs8XI3YCAywIOlYL
+AAKWbAAHlIMAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMA
+FiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2
+AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTW
+BgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMs
+CCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc1
+1gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQID
+zxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00E
+NdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQAB
+A00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAA
+ADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2
+AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwA
+FPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYB
+AAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMs
+CDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMA
+FiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2
+AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTW
+BgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMs
+CCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc1
+1gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQID
+zxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00E
+NdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQAB
+A00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAA
+ADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2
+AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwA
+FPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYB
+AAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMs
+CDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMA
+FiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2
+AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTW
+BgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMs
+CCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc1
+1gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQID
+zxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00E
+NdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQAB
+A00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAA
+ADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2
+AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwA
+FPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYB
+AAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMs
+CDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMA
+FiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2
+AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTW
+BgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMs
+CCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc1
+1gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQID
+zxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00E
+NdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQAB
+A00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAA
+ADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2
+AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwA
+FPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYB
+AAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMs
+CDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMA
+FiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2
+AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTW
+BgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMs
+CCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc1
+1gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQID
+zxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00E
+NdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQAB
+A00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAA
+ADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2
+AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwA
+FPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYB
+AAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMs
+CDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMA
+FiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2
+AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTW
+BgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMs
+CCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc1
+1gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQABA00ENdYFAQID
+zxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00E
+NdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2AANoATXWBQAB
+A00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwAFPYBAAAX9gAA
+ADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYBAAAAAZYAACF2
+AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMsCDpWCwAClmwA
+FPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAHMAFiQBFyQBSWYB
+AAAAAZYAACF2AANoATXWBQABA00ENdYFAQIDzxc11gUCAwMsCCN2AAFNBCN2AQLPFyN2AgMs
+CDpWCwAClmwAFPYBAAAX9gAAADXWBQABA00ENdYFAQIDzxc11gUCAwMsCDTWBgABBQAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAIYCFwASAAEAnAAPAAQAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAEQAAEDx/wIARAAMAAAAAAAAAAAABgBOAG8AcgBtAGEAbAAAAAIAAAAcAENK
+GABPSgIAUUoCAF9IAQRtSAkIc0gJCHRICQg4AAFAAQACADgADAAAAAAAAAAAAAkASABlAGEA
+ZABpAG4AZwAgADEAAAAIAAEABiQBQCYAAwA1CIEAXgACQAEAAgBeAAwAAAAAAAAAAAAJAEgA
+ZQBhAGQAaQBuAGcAIAAyAAAALgACAAMkAwYkAQ3GBQABHAIADoSU/w+EHAIRhOT9QCYBXYSU
+/16EHAJghOT9YSQDAwA1CIEAAAAAAAAAAAAAAAAAAABEAEFA8v+hAEQADAEAAAAAAAAAABYA
+RABlAGYAYQB1AGwAdAAgAFAAYQByAGEAZwByAGEAcABoACAARgBvAG4AdAAAAAAAVgBpQPP/
+swBWAAwFAAAAAAAAAAAMAFQAYQBiAGwAZQAgAE4AbwByAG0AYQBsAAAAIAA6VgsAF/YDAAA0
+1gYAAQUDAAA01gYAAQoDbABh9gMAAAIACwAAACgAawD0/8EAKAAABQAAAAAAAAAABwBOAG8A
+IABMAGkAcwB0AAAAAgAMAAAAAAAwAD5AAQDyADAADAAAAAAAAAAAAAUAVABpAHQAbABlAAAA
+CAAPAAMkAWEkAQMANQiBADIAQkABAAIBMgAMAAAAAAAAAAAACQBCAG8AZAB5ACAAVABlAHgA
+dAAAAAIAEAADADUIgQA2AEpAAQASATYADAAAAAAAAAAAAAgAUwB1AGIAdABpAHQAbABlAAAA
+CAARAAMkAWEkAQMANQiBAFQAQ0ABACIBVAAMAAAAAAAAAAAAEABCAG8AZAB5ACAAVABlAHgA
+dAAgAEkAbgBkAGUAbgB0AAAAGgASAA3GBQABBwJAD4QHAhGE+f1ehAcCYIT5/QAAUABSQAEA
+MgFQAAwAAAAAAAAAAAASAEIAbwBkAHkAIABUAGUAeAB0ACAASQBuAGQAZQBuAHQAIAAyAAAA
+EgATAA+E6/8RhBUAXoTr/2CEFQAAADQAH0ABAEIBNAAMAAAAAAAAAAAABgBIAGUAYQBkAGUA
+cgAAAA0AFAANxggAAjkQciABAgAAADQAIEABAFIBNAAMAAAAAAAAAAAABgBGAG8AbwB0AGUA
+cgAAAA0AFQANxggAAjkQciABAgAAAC4AKUCiAGEBLgAMAAAAAAAAAAAACwBQAGEAZwBlACAA
+TgB1AG0AYgBlAHIAAAAAAAAAAABqHgAABQAAiAAAAAD/////AAAAAEMAAACAAAAAgQAAACEB
+AAAiAQAAJwEAACgBAAAvAQAAMAEAADEBAAA+AQAAPwEAAEABAABBAQAAQgEAAEMBAABEAQAA
+RwEAAFEBAABSAQAAUwEAAFQBAABVAQAAVgEAAFcBAABYAQAAiAEAAIkBAACKAQAAiwEAAIwB
+AACNAQAAjgEAAJEBAACvAQAAsAEAALEBAACyAQAAswEAALQBAAC1AQAAtgEAAJcDAACYAwAA
+mQMAAJoDAACbAwAAnAMAAJ0DAACmAwAApwMAAKgDAACpAwAAqgMAAKsDAACuAwAAxAMAAMUD
+AADGAwAAxwMAAMgDAADJAwAAygMAAMsDAADMBQAAzQUAAM4FAADPBQAA0AUAANEFAADSBQAA
+2gUAANsFAADcBQAA4wUAAOQFAADlBQAA5gUAAOcFAADoBQAA6wUAABMGAAAUBgAAFQYAABYG
+AAAXBgAAGAYAABkGAAAaBgAAGwcAABwHAAAdBwAAHgcAAB8HAAAgBwAAIQcAACIHAAAnBwAA
+KAcAAC8HAAAwBwAAMQcAADIHAAAzBwAANAcAADcHAABXBwAAWAcAAFkHAABaBwAAWwcAAFwH
+AABdBwAAXgcAAKAIAAChCAAAoggAAKMIAACrCAAArAgAAK0IAACuCAAArwgAALAIAACzCAAA
+3ggAAN8IAADgCAAA4QgAAOIIAADjCAAA5AgAAOUIAAAaCQAARQoAAEYKAABHCgAASAoAAEkK
+AABKCgAASwoAAE4KAABxCgAAcgoAAHMKAAB0CgAAdQoAAHYKAAB3CgAAeAoAAEQMAABFDAAA
+RgwAAEcMAABIDAAASQwAAEoMAABNDAAAcAwAAHEMAAByDAAAcwwAAHQMAAB1DAAAdgwAAHcM
+AAB4DAAAeQwAAHoMAAB7DAAAoA8AAKEPAACiDwAAow8AAKQPAAClDwAApg8AAKcPAACoDwAA
+qQ8AAKoPAACrDwAArA8AAK0PAACuDwAAtQ8AALYPAAC3DwAAuA8AALkPAAC6DwAAuw8AAL4P
+AADdDwAA3g8AAN8PAADgDwAA4Q8AAOIPAADjDwAA5A8AAPoRAAD7EQAA/BEAAP0RAAD+EQAA
+/xEAAAASAAABEgAAAhIAAAMSAAAIEgAACRIAAAoSAAALEgAADBIAAA0SAAAREgAALBIAAC0S
+AAAuEgAALxIAADASAAAxEgAAMhIAADMSAAAUFAAAFRQAABYUAAAXFAAAGBQAABkUAAAaFAAA
+GxQAAB4UAAAlFAAAJhQAACcUAAAoFAAAKRQAACoUAAAuFAAATRQAAE4UAABPFAAAUBQAAFEU
+AABSFAAAUxQAAFQUAACkFQAApRUAAKYVAACnFQAAzhUAAM8VAADQFQAA0RUAANIVAADTFQAA
+1xUAAAAWAAABFgAAAhYAAAMWAAAEFgAABRYAAAYWAAAHFgAAChgAAAsYAAAMGAAADRgAAA4Y
+AAAPGAAAEBgAABEYAAASGAAAFRgAABYYAAAXGAAAGBgAABkYAAAaGAAAGxgAABwYAAAhGAAA
+IhgAACkYAAAqGAAAKxgAACwYAAAtGAAALhgAADIYAABHGAAASBgAAEkYAABKGAAASxgAAEwY
+AABNGAAAThgAAJAZAACRGQAAkhkAAJMZAACUGQAAlRkAAJYZAACaGQAArxkAALAZAACxGQAA
+shkAALMZAAC0GQAAtRkAALYZAABZGwAAWhsAAFsbAABcGwAAXRsAAF4bAABfGwAAYBsAAGcb
+AABoGwAAaRsAAGobAABrGwAAbBsAAG0bAAB8GwAAfRsAAH4bAAB/GwAAgBsAAIEbAACCGwAA
+hhsAAK4bAACvGwAAsBsAALEbAACyGwAAsxsAALQbAAC1GwAA3B0AAN0dAADeHQAA3x0AAOAd
+AADhHQAA4h0AAOMdAADkHQAA5R0AAOYdAADqHQAA6x0AAOwdAADuHQAA7x0AAPEdAADyHQAA
+9B0AAPUdAAD3HQAA+B0AAAceAAAIHgAACR4AABQeAABjHgAAZB4AAGseAABJiAAwAAAAAAAA
+AAABAAAAAAAAAAAwAAAAAAABSYgAMAAAAAAAAAAAAQAAAAAAAAAAAAAAAACAAUmIADAAAAAA
+AAAAAAEAAAAAAAAAAAAAAAAAgAFJiAAwAAAAAAAAAAABAAAAAAAAAAAAAAAAAIABSYgAMAAA
+AAAAAAAAAQAAAAAAAAAAAAAAAACAAUmIADAAAAAAAAAAAAEAAAAAAAEAAAAAAAAAoAFJiAAw
+BgAAAAAAAAACAAAAAQABAAAAAAAAACABSYgAMAYAAAAAAAAAAgAAAAEAAQAAAAAAAAAgAdAC
+AABWIAAA/////wAAAAAAAAEAAAQAAAAAoAFJiAAwCAAAAAAAAAACAAAAAQABAAAAAAAAACAB
+SYgAMAgAAAAAAAAAAgAAAAEAAQAAAAAAAAAgAUmIADAIAAAAAAAAAAIAAAABAAEAAAAAAAAA
+IAGcEAAAAAAAAAAAAAAAANkQAAABAAAEAAAAAKABSYgAMAoAAAAAAAAAAgAAAAEAAQAAAAAA
+AAAgAUmIADAKAAAAAAAAAAIAAAABAAEAAAAAAAAAIAFJiAAwCgAAAAAAAAACAAAAAQABAAAA
+AAAAACABHQAziwMAAAAAANC4MQAAAAAAAQAABAAAAACgAUmIADAMAAAAAAAAAAIAAAABAAEA
+AAAAAAAAIAFJiAAwDAAAAAAAAAACAAAAAQABAAAAAAAAACABSYgAMAwAAAAAAAAAAgAAAAEA
+AQAAAAAAAAAgAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAQAAAAAoAFJiAAwDgAAAAAAAAACAAAA
+AQABAAAAAAAAACABSYgAMA4AAAAAAAAAAgAAAAEAAQAAAAAAAAAgAUmIADAOAAAAAAAAAAIA
+AAABAAEAAAAAAAAAIAEFAgAAAAD/////AAAAAP//mp0BAACMAAAAAKABSYgAMBAAAAAAAAAA
+AgAAAAEAAQAAAAAAAAAgAUmIADAQAAAAAAAAAAIAAAABAAEAAAAAAAAAIAFJiAAwEAAAAAAA
+AAACAAAAAQABAAAAAAAAACAB/f8AwIABAgCAAAAAAQAAAAAAAQAABAAAAACgAUmIADASAAAA
+AAAAAAIAAAABAAEAAAAAAAAAIAFJiAAwEgAAAAAAAAACAAAAAQABAAAAAAAAACABSYgAMBIA
+AAAAAAAAAgAAAAEAAQAAAAAAAAAgAQAAViAAAP////8AAAAAAAAAAAEAAAQAAAAAoAFJiAAw
+FAAAAAAAAAACAAAAAQABAAAAAAAAACABSYgAMBQAAAAAAAAAAgAAAAEAAQAAAAAAAAAgAUmI
+ADAUAAAAAAAAAAIAAAABAAEAAAAAAAAAIAEAAAAAmp0FANPKFABm3hkA+NUBAAAEAAAAAKAB
+SYgAMBYAAAAAAAAAAgAAAAEAAQAAAAAAAAAgAUmIADAWAAAAAAAAAAIAAAABAAEAAAAAAAAA
+IAFJiAAwFgAAAAAAAAACAAAAAQABAAAAAAAAACABAAAAAAAAAAAAAAAAAAAAAAAAAQAABAAA
+AACgAakAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQ
+AAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACA
+AQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACA
+AAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAA
+AIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAAADAAAAAA
+AAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAA
+AAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAw
+AAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAA
+EDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkA
+AAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAA
+qQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAA
+AACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAA
+AAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQ
+AAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACA
+AQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAAADAAAAAAAAAAgAAA
+AIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACA
+AAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAwAAAAAAAA
+AIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAA
+AAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAA
+AAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAw
+AAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAA
+ADAAAAAAAAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkA
+AAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAA
+mQAAAAAwAAAAAAAAAIAAAACAAQAA1AAAAAAgAJgAAAAAMAAAAAAAAACAAAAAgAAAAAAAAAAA
+AACYAAAAADAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIAAAACAAAAAAAAA
+AAAAAJgAAAAAMAAAAAAAAACAAAAAgAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgAAAAIAAAAAA
+AAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAwAAAAAAAAAIAAAACA
+AQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACA
+AAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAA
+AIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAAADAAAAAA
+AAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAA
+AAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAw
+AAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAA
+EDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkA
+AAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAA
+qQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACAAAAAgAEAANQAAAAA
+IACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAA
+AAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAAADAAAAAAAAAAgAAAAIABAADU
+AAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAwAAAAAAAAAIAAAACA
+AQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACA
+AAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAA
+AIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAA
+AAAAgAAAAIABAADQAAAAACAAmQAAAAAwAAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAA
+AAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAw
+AAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAA
+EDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkA
+AAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAAADAAAAAAAAAAgAAAAIABAADUAAAAACAA
+qQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAA
+IACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAwAAAAAAAAAIAAAACAAQAA1AAA
+AAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQ
+AAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACAAAAAgAEA
+ANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACA
+AQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAAADAAAAAAAAAAgAAA
+AIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACA
+AAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAwAAAAAAAA
+AIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAA
+AAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAA
+AAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAw
+AAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAA
+ADAAAAAAAAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkA
+AAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAA
+qQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAA
+AACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAA
+AAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQ
+AAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACA
+AQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACA
+AAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAwAAAAAAAA
+AIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAA
+AAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAA
+AAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAw
+AAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAA
+ADAAAAAAAAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkA
+AAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAA
+mQAAAAAwAAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAA
+IACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAA
+AAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQ
+AAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACA
+AQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACA
+AAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAA
+AIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAAADAAAAAA
+AAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAA
+AAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAw
+AAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAA
+EDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkA
+AAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAA
+qQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAA
+AACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAA
+AAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQ
+AAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACA
+AQAA0AAAAAAgAJkAAAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACA
+AAAAgAEAANAAAAAAIACZAAAAADAAAAAAAAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAA
+AIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAA
+AAAAgAAAAIABAADQAAAAACAAmQAAAAAwAAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAA
+AAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAw
+AAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAA
+EDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkA
+AAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAA
+qQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAA
+IACZAAAAADAAAAAAAAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAA
+AAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQ
+AAAAACAAmQAAAAAwAAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACA
+AQAA0AAAAAAgAJkAAAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACA
+AAAAgAEAANAAAAAAIACZAAAAADAAAAAAAAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAA
+AIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAA
+AAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAA
+AAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAw
+AAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAA
+EDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkA
+AAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAA
+mQAAAAAwAAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAA
+IACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAA
+AAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAAADAAAAAAAAAAgAAAAIABAADU
+AAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAwAAAAAAAAAIAAAACA
+AQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACA
+AAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAA
+AIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACZAAAAADAAAAAA
+AAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAA
+AAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAw
+AAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAA
+EDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkA
+AAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAA
+qQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAA
+IACZAAAAADAAAAAAAAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAA
+AAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQ
+AAAAACAAmQAAAAAwAAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACA
+AQAA0AAAAAAgAJkAAAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACA
+AAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAA
+AIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAA
+AAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAA
+AAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAmQAAAAAw
+AAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAA
+EDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkA
+AAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAA
+qQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAA
+IACZAAAAADAAAAAAAAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAA
+AAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQ
+AAAAACAAmQAAAAAwAAAAAAAAAIAAAACAAQAA1AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEA
+ANAAAAAAIACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACA
+AQAA0AAAAAAgAJkAAAAAMAAAAAAAAACAAAAAgAEAANQAAAAAIACpAAAAEDAAAAAAAAAAgAAA
+AIABAADQAAAAACAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACA
+AAAAgAEAANAAAAAAIACZAAAAADAAAAAAAAAAgAAAAIABAADUAAAAACAAqQAAABAwAAAAAAAA
+AIAAAACAAQAA0AAAAAAgAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAIACpAAAAEDAAAAAA
+AAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAA
+AAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAw
+AAAAAAAAAIAAAACAAQAA0AAAAAAAAKkAAAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAA
+EDAAAAAAAAAAgAAAAIABAADQAAAAAAAAqQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAAAKkA
+AAAQMAAAAAAAAACAAAAAgAEAANAAAAAAAACpAAAAEDAAAAAAAAAAgAAAAIABAADQAAAAAAAA
+qQAAABAwAAAAAAAAAIAAAACAAQAA0AAAAAAgAJkAAAAAMAAAAAAAAACAAAAAgAEAANQAAAAA
+IACYAAAAADAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAScgAMAAAAAAAAAAAAgAAAAEAAAAAAAAA
+AABOB2iLADAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAngdJyAAwAAAAAAAAAAACAAAAAQAAAAAA
+AAAAAE4HaIsAMAAAAAAAAAAAAQAAAAAAAAAAAAAAAACeB0nIADAAAAAAAAAAAAIAAAABAAAA
+AAAAAAAATgdoiwAwAAAAAAAAAAABAAAAAAAAAAAAAAAAAJ4HScgAMAAAAAAAAAAAAgAAAAEA
+AAAAAAAAAABOB2iLADAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAngdJyAAwAAAAAAAAAAACAAAA
+AQAAAAABAAAAAE4HScgAMAAAAAAAAAAAAgAAAAEAAAAAAAAAAABOA0nIADAAAAAAAAAAAAIA
+AAABAAAAAAAAAAAATgOQQAAAFTAAAAAAAAAAgAAAAIAAAAABAAAAAAAHmEAAABUwAAAAAAAA
+AIAAAACAAAAAAAAAAAAAB0nIADAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAoANoiwAwAAAAAAAA
+AAABAAAAAAAAAAAAAABkCZ4HAAAAAAMAAAAGAAAABgAAAAkAAAAMAAAADAAAAA4AAAAQAAAA
+HQAAAHgAAAB6AAAAfAAAAH8AAAAABgAALxoAAGomAAAUAAAAMwAAAAAGAAA/CQAAUQkAAFgJ
+AACOCQAAtAkAAKYLAACrCwAAyQsAAOMNAADoDQAAGA4AAB0PAAAzDwAAWQ8AAKsQAACwEAAA
+4xAAAEcSAAByEgAAdxIAAEkUAAByFAAAeRQAAK0XAAC6FwAA4RcAAAgaAAAtGgAAFBwAACkc
+AABSHAAAzh0AAAAeAAAHHgAAGyAAACwgAABLIAAAkyEAALEhAABnIwAAfCMAAK4jAAC1IwAA
+8SUAAGomAAAVAAAAFwAAABgAAAAZAAAAGgAAABsAAAAcAAAAHQAAAB4AAAAfAAAAIAAAACEA
+AAAiAAAAIwAAACQAAAAlAAAAJgAAACcAAAAoAAAAKQAAACoAAAArAAAALAAAAC0AAAAuAAAA
+LwAAADAAAAAxAAAAMgAAADQAAAA1AAAANgAAADcAAAA4AAAAOQAAADoAAAA7AAAAPAAAAD0A
+AAA+AAAAPwAAAEAAAABBAAAAQgAAAEMAAAAABgAAaSYAABYAAAAQAAAAFwAAABkAAAAdAAAA
+JAAAACYAAAB/AAAAEyF0/5WAEyF0/5WA//8CAAAABgBfo9MKEAABAKS8BhMGAGCj0woRAAEA
+RKUjAP0FAAD9BQAAax4AAAAAAAACAAEAAAACAAUGAAAFBgAAax4AAAAAAAABAAAAAgAAADkA
+AAACAAAAKoB1cm46c2NoZW1hcy1taWNyb3NvZnQtY29tOm9mZmljZTpzbWFydHRhZ3MFgHBs
+YWNlAIA4AAAAAQAAACqAdXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6c21hcnR0
+YWdzBIBDaXR5AIAMAAAB/Jt2CQAAAAACAAAAAAABAAAAAAAAAAAAAgQAAAsEAAC+DwAAxA8A
+AEYQAABMEAAALxEAADURAABJEwAATRMAALIYAAC6GAAAQxkAAEsZAADrHQAA7B0AAOwdAADu
+HQAA7h0AAO8dAADvHQAA8R0AAPIdAAD0HQAA9R0AAPcdAAD8HQAABh4AAAkeAAATHgAAJR4A
+AGIeAABkHgAAaB4AAGseAAAHABwABwAcAAcAHAAHABwABwAcAAcAHAAHABwABwACAAQABwAE
+AAIABAAHAAQABwAEAAcABAAHAAcABwAHAAQABwAEAAIAAAAAAHAAAAB0AAAA6QIAAO0CAABn
+BQAAawUAAIEFAACFBQAAPQgAAEEIAAAbCQAAHgkAAHIUAACCFAAAThgAAFIYAABNGQAAWxkA
+AAYcAAAKHAAA6x0AAOwdAADsHQAA7h0AAO4dAADvHQAA7x0AAPEdAADyHQAA9B0AAPUdAAD3
+HQAA/B0AAAYeAAAJHgAAEx4AACUeAABiHgAAZB4AAGgeAABrHgAABwAzAAcAMwAHADMABwAz
+AAcAMwAHADMABwAzAAcAMwAHADMABwAzAAcAAgAEAAcABAACAAQABwAEAAcABAAHAAQABwAH
+AAcABwAEAAcABAACAAAAAADrHQAA7B0AAOwdAADuHQAA7h0AAO8dAADvHQAA8R0AAPIdAAD0
+HQAA9R0AAPcdAAD8HQAABh4AAAkeAAATHgAAJR4AAGIeAABkHgAAaB4AAGseAAADAAIABAAC
+AAQAAgAEAAIABAACAAQAAgAEAAcAAgAHAAIABAACAAQAAgAAAAAA6x0AAOwdAADsHQAA7h0A
+AO4dAADvHQAA7x0AAPEdAADyHQAA9B0AAPUdAAD3HQAA/B0AACUeAABiHgAAZB4AAGgeAABr
+HgAABwACAAQABwAEAAIABAAHAAQABwAEAAcABAAHAAQABwAEAAIAJwD+KeYEAQAJBP8PAAAA
+AAAAAAAAAAAAAAAAAAEASko8DgEACQT/DwAAAAAAAAAAAAAAAAAAAAABAABgBQ9QSUJr/w8A
+AAAAAAAAAAAAAAAAAAAAAQAWOagTUElCa/8PAAAAAAAAAAAAAAAAAAAAAAEAPygtFAEACQT/
+DwAAAAAAAAAAAAAAAAAAAAABAAYFCh8BAAkE/w8AAAAAAAAAAAAAAAAAAAAAAQBsaigfAQAJ
+BP8PAAAAAAAAAAAAAAAAAAAAAAEAOxdcIFBJQmv/DwAAAAAAAAAAAAAAAAAAAAABAKRZpS1Q
+SUJr/w8AAAAAAAAAAAAAAAAAAAAAAQDvL20vAQAJBP8PAAAAAAAAAAAAAAAAAAAAAAEAViFw
+LwEACQT/DwAAAAAAAAAAAAAAAAAAAAABAPlT3jEBAAkE/w8AAAAAAAAAAAAAAAAAAAAAAQAX
+QuQxUElCa/8PAAAAAAAAAAAAAAAAAAAAAAEABQT3MQEACQT/DwAAAAAAAAAAAAAAAAAAAAAB
+APB4GjQBAAkE/w8AAAAAAAAAAAAAAAAAAAAAAQDyciA3AQAJBP8PAAAAAAAAAAAAAAAAAAAA
+AAEAsWhSOwEACQT/DwAAAAAAAAAAAAAAAAAAAAABAOF0cj0BAAkE/w8AAAAAAAAAAAAAAAAA
+AAAAAQAKJHY9AQAJBP8PAAAAAAAAAAAAAAAAAAAAAAEA3DQ4PgEACQT/DwAAAAAAAAAAAAAA
+AAAAAAABALlh7j4BAAkE/w8AAAAAAAAAAAAAAAAAAAAAAQC/IYZAAQAJBP8PAAAAAAAAAAAA
+AAAAAAAAAAEA915jQwEACQT/DwAAAAAAAAAAAAAAAAAAAAABAHI4tEZ+4LBr/w//D/8P/w//
+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
+AAAAAAAAAAAAAAAA////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//////////8nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8nAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAABAAAAAQAAAAIAAAA5QAAAAAAAAADAAAAqj46ALRhPwBCP2MA1kXKAAAA
+AAAhAQAAIgEAACcBAAAoAQAALwEAADABAAAxAQAAPgEAAD8BAABAAQAAQQEAAEIBAABDAQAA
+RAEAAEcBAABRAQAAUgEAAFMBAABUAQAAVQEAAFYBAABXAQAAWAEAAIgBAACJAQAAigEAAIsB
+AACMAQAAjQEAAI4BAACRAQAArwEAALABAACxAQAAsgEAALMBAAC0AQAAtQEAALYBAACXAwAA
+pgMAAKcDAACoAwAAqQMAAKoDAACrAwAArgMAAMQDAADFAwAAxgMAAMcDAADIAwAAyQMAAMoD
+AADLAwAAzAUAAOMFAADkBQAA5QUAAOYFAADnBQAA6AUAAOsFAAATBgAAFAYAABUGAAAWBgAA
+FwYAABgGAAAZBgAAGgYAABsHAAAcBwAAHQcAACEHAAAiBwAAJwcAACgHAAAvBwAAMAcAADEH
+AAAyBwAAMwcAADQHAAA3BwAAVwcAAFgHAABZBwAAWgcAAFsHAABcBwAAXQcAAF4HAACgCAAA
+qwgAAKwIAACtCAAArggAAK8IAACwCAAAswgAAN4IAADfCAAA4AgAAOEIAADiCAAA4wgAAOQI
+AADlCAAARQoAAEYKAABHCgAASAoAAEkKAABKCgAASwoAAE4KAABxCgAAcgoAAHMKAAB0CgAA
+dQoAAHYKAAB3CgAAeAoAAEQMAABFDAAARgwAAEcMAABIDAAASQwAAEoMAABNDAAAcAwAAHEM
+AAByDAAAcwwAAHQMAAB1DAAAdgwAAHcMAAB4DAAAeQwAAHoMAAB7DAAAoA8AALYPAAC3DwAA
+uA8AALkPAAC6DwAAuw8AAL4PAADdDwAA3g8AAN8PAADgDwAA4Q8AAOIPAADjDwAA5A8AAPoR
+AAAIEgAACRIAAAoSAAALEgAADBIAAA0SAAAREgAALBIAAC0SAAAuEgAALxIAADASAAAxEgAA
+MhIAADMSAAAUFAAAJRQAACYUAAAnFAAAKBQAACkUAAAqFAAALhQAAE0UAABOFAAATxQAAFAU
+AABRFAAAUhQAAFMUAABUFAAApBUAAM4VAADPFQAA0BUAANEVAADSFQAA0xUAANcVAAAAFgAA
+ARYAAAIWAAADFgAABBYAAAUWAAAGFgAABxYAAAoYAAAWGAAAFxgAABgYAAAZGAAAGxgAABwY
+AAAhGAAAIhgAACkYAAAqGAAAKxgAACwYAAAtGAAALhgAADIYAABHGAAASBgAAEkYAABKGAAA
+SxgAAEwYAABNGAAAThgAAJAZAACRGQAAkhkAAJMZAACUGQAAlRkAAJYZAACaGQAArxkAALAZ
+AACxGQAAshkAALMZAAC0GQAAtRkAALYZAABZGwAAZxsAAGgbAABpGwAAahsAAGsbAABsGwAA
+bRsAAHwbAAB9GwAAfhsAAH8bAACAGwAAgRsAAIIbAACGGwAArhsAAK8bAACwGwAAsRsAALIb
+AACzGwAAtBsAALUbAADcHQAA6h0AAOsdAADsHQAA7h0AAPEdAAD0HQAA9x0AAGQeAABrHgAA
+AAAAACEBAAAIAAAAAgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIB
+AACeAQAFAgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAF
+AgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIB
+AAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAA
+ngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIB
+AAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACWAQAFIQcAAAgAAAACAQAAAgEAAAIBAACeAQAF
+AgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIB
+AAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAA
+ngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIB
+AAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIBAAACAQAA
+AgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4B
+AAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAA
+AgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIB
+AACeAQAFAgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAF
+AgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIB
+AAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAA
+ngEABQIBAAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIB
+AAACAQAAAgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIBAAACAQAA
+AgEAAJ4BAAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4B
+AAUCAQAAAgEAAAIBAACeAQAFAgEAAAIBAAACAQAAngEABQIBAAACAQAAAgEAAJ4BAAUCAQAA
+AgEAAAIBAACeAQAFAgEAAAIBAAACAQAAlgEABQAAAAABAAAAAQAAAAEAAAABAAAAAQAAAP9A
+AIABAAAAAAAAAAAAdCryDwEAAQAAAAAAAAAAAAAAAAAAAAAAAhAAAAAAAAAAah4AAFAAABAA
+QAAA//8BAAAABwBVAG4AawBuAG8AdwBuAP//AQAIAAAAAAAAAAAAAAD//wEAAAAAAP//AAAC
+AP//AAAAAP//AAACAP//AAAAAAMAAABHFpABAAACAgYDBQQFAgMEh3oAIAAAAIAIAAAAAAAA
+AP8BAAAAAAAAVABpAG0AZQBzACAATgBlAHcAIABSAG8AbQBhAG4AAAA1FpABAgAFBQECAQcG
+AgUHAAAAAAAAABAAAAAAAAAAAAAAAIAAAAAAUwB5AG0AYgBvAGwAAAAzJpABAAACCwYEAgIC
+AgIEh3oAIAAAAIAIAAAAAAAAAP8BAAAAAAAAQQByAGkAYQBsAAAAIgAEADEIiBgA8NACAABo
+AQAAAAA4E9xmOBPcZjVMiYYCAAIAAAB3BAAAdRkAAAEADwAAAAQAgxA2AAAAdwQAAHUZAAAB
+AA8AAAA2AAAAAAAAACEDAPAQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAKUGwAe0ALQAgAASNAAAEAAZAGQAAAAZAAAA3R0AAN0dAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAA
+AAAAADKDEQDwEATf3wMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIWAAAAAAI8P8PAQAB
+PwAA5AQAAP///3////9/////f////3////9/////f////3+0YT8AAAAAADIAAAAAAAAAAAAA
+AAAAAAAAAP//EgAAAAAAAAAwAEEAYwB0AGkAbwBuAHMAIABBAHIAaQBzAGkAbgBnACAARgBy
+AG8AbQAgAEMAaABpAGUAZgAgAE8AZgBmAGkAYwBlAHIAIABHAHIAbwB1AHAAIABNAGUAZQB0
+AGkAbgBnAAAAAAAAAAUASABXAGEAcgBpAAQAOAAwADYANQAAAAAAAAAAAAAAAAAAAAAAAAAA
+AKgAAAAGAAAAJwAAAAAADAABAAwAAgAMAAMADAAEAAwABQAMAAYADAAHAAwACAAMAAkADAAK
+AAwACwAMAAwADAANAAwADgAMAA8ADAAQAAwAEQAMABIADAATAAwAFAAMABUADAAWAAwAFwAM
+ABgADAAZAAwAGgAMABsADAAcAAwAHQAMAB4ADAAfAAwAIAAMACEADAAiAAwAIwAMACQADAAl
+AAwAJgAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAA/v8AAAUBAgAAAAAAAAAAAAAAAAAAAAAAAQAAAOCFn/L5T2gQ
+q5EIACsns9kwAAAAmAEAABEAAAABAAAAkAAAAAIAAACYAAAAAwAAANQAAAAEAAAA4AAAAAUA
+AADwAAAABwAAAPwAAAAIAAAADAEAAAkAAAAcAQAAEgAAACgBAAAKAAAASAEAAAsAAABUAQAA
+DAAAAGABAAANAAAAbAEAAA4AAAB4AQAADwAAAIABAAAQAAAAiAEAABMAAACQAQAAAgAAAOQE
+AAAeAAAANAAAAEFjdGlvbnMgQXJpc2luZyBGcm9tIENoaWVmIE9mZmljZXIgR3JvdXAgTWVl
+dGluZwAAAAAeAAAABAAAAAAAAAAeAAAACAAAAEhXYXJpAAAAHgAAAAQAAAAAAAAAHgAAAAgA
+AABOb3JtYWwAAB4AAAAIAAAAODA2NQAAAAAeAAAABAAAADIAAAAeAAAAGAAAAE1pY3Jvc29m
+dCBPZmZpY2UgV29yZAAAAEAAAAAAjIZHAAAAAEAAAAAA/r52jZbEAUAAAAAAsPXLTnPKAUAA
+AAAAsPXLTnPKAQMAAAABAAAAAwAAAHcEAAADAAAAdRkAAAMAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAP7/AAAFAQIAAAAAAAAAAAAAAAAAAAAAAAIAAAAC1c3VnC4bEJOXCAArLPmu
+RAAAAAXVzdWcLhsQk5cIACss+a5sAQAAKAEAAAwAAAABAAAAaAAAAA8AAABwAAAABQAAAIwA
+AAAGAAAAlAAAABEAAACcAAAAFwAAAKQAAAALAAAArAAAABAAAAC0AAAAEwAAALwAAAAWAAAA
+xAAAAA0AAADMAAAADAAAAAkBAAACAAAA5AQAAB4AAAAUAAAASHVtYmVyc2lkZSBQb2xpY2UA
+AAADAAAANgAAAAMAAAAPAAAAAwAAAN0dAAADAAAAqBkLAAsAAAAAAAAACwAAAAAAAAALAAAA
+AAAAAAsAAAAAAAAAHhAAAAEAAAAxAAAAQWN0aW9ucyBBcmlzaW5nIEZyb20gQ2hpZWYgT2Zm
+aWNlciBHcm91cCBNZWV0aW5nAAwQAAACAAAAHgAAAAYAAABUaXRsZQADAAAAAQAAAADMBQAA
+EwAAAAAAAACgAAAAAQAAADUCAAACAAAAPQIAAAMAAACpAgAABAAAAMkCAAAFAAAA4QIAAAYA
+AAD9AgAABwAAACUDAAAIAAAAUQMAAAkAAAB5AwAACgAAAJUDAAALAAAASQQAAAwAAABVBAAA
+DQAAAHEEAAAOAAAAmQQAAA8AAADFBAAAEAAAAO0EAAARAAAACQUAABIAAAC9BQAAEQAAAAIA
+AAAJAAAARnVsbE5hbWUAAwAAACIAAABQcm90ZWN0aXZlIE1hcmtpbmcgQ2xhc3NpZmljYXRp
+b24ABAAAABYAAABBZGRpdGlvbmFsIERlc2NyaXB0b3IABQAAABEAAABDcmVhdG9yIFVzZXJO
+YW1lAAYAAAASAAAAQ3JlYXRvciBVc2VyVGl0bGUABwAAABMAAABDcmVhdG9yIE9mZmljZU5h
+bWUACAAAABMAAABDcmVhdG9yIERlcGFydG1lbnQACQAAABAAAABDcmVhdG9yIENvbXBhbnkA
+CgAAABcAAABDcmVhdG9yIFByb3h5QWRkcmVzc2VzAAsAAAAQAAAAQ3JlYXRvciBNYW5hZ2Vy
+AAwAAAAJAAAAVXNlck5hbWUADQAAAAoAAABVc2VyVGl0bGUADgAAAAsAAABPZmZpY2VOYW1l
+AA8AAAALAAAARGVwYXJ0bWVudAAQAAAACAAAAENvbXBhbnkAEQAAAA8AAABQcm94eUFkZHJl
+c3NlcwASAAAACAAAAE1hbmFnZXIAAgAAAOQEAAAeAAAAZAAAAEM6XERvY3VtZW50cyBhbmQg
+U2V0dGluZ3NcODA2NVxMb2NhbCBTZXR0aW5nc1xUZW1wb3JhcnkgSW50ZXJuZXQgRmlsZXNc
+T0xLQzRcQ09HIE1pbnMgOCA5IDA0LmRvYwAAAAAeAAAAGAAAAE5PVCBQUk9URUNUSVZFTFkg
+TUFSS0VEAB4AAAAQAAAATk8gREVTQ1JJUFRPUgAAAB4AAAAUAAAAV2FsbGVyLCBQYXVsIDgw
+NjUAAAAeAAAAIAAAAEluZm9ybWF0aW9uIENvbXBsaWFuY2UgT2ZmaWNlcgAAHgAAACQAAABJ
+Q1UgLSBEYXRhIFByb3RlY3Rpb24vRk9JIChDREItT1MpAAAeAAAAIAAAAENvcnBvcmF0ZSBE
+ZXZlbG9wbWVudCBCcmFuY2gAAAAAHgAAABQAAABIdW1iZXJzaWRlIFBvbGljZQAAAB4AAACs
+AAAAQ0NNQUlMOldhbGxlciwgUGF1bCBhdCBOb3J0aGJhbmt8TVM6SFVNQkVSU0lERS9OT1JU
+SEJBTksvUEFVTFdBTExFUnxTTVRQOnh4eHgueHh4eHh4QHh4eHh4eHh4eHgueHh4Lnh4eHh4
+eC54eHxYNDAwOmM9R0I7YT0gO3A9SHVtYmVyc2lkZSBQb2xpYztvPU5vcnRoYmFuaztzPVdh
+bGxlcjtnPVBhdWw7AB4AAAAEAAAAAAAAAB4AAAAUAAAAV2FsbGVyLCBQYXVsIDgwNjUAAAAe
+AAAAIAAAAEluZm9ybWF0aW9uIENvbXBsaWFuY2UgT2ZmaWNlcgAAHgAAACQAAABJQ1UgLSBE
+YXRhIFByb3RlY3Rpb24vRk9JIChDREItT1MpAAAeAAAAIAAAAENvcnBvcmF0ZSBEZXZlbG9w
+bWVudCBCcmFuY2gAAAAAHgAAABQAAABIdW1iZXJzaWRlIFBvbGljZQAAAB4AAACsAAAAQ0NN
+QUlMOldhbGxlciwgUGF1bCBhdCBOb3J0aGJhbmt8TVM6SFVNQkVSU0lERS9OT1JUSEJBTksv
+UEFVTFdBTExFUnxTTVRQOnh4eHgueHh4eHh4QHh4eHh4eHh4eHgueHh4Lnh4eHh4eC54eHxY
+NDAwOmM9R0I7YT0gO3A9SHVtYmVyc2lkZSBQb2xpYztvPU5vcnRoYmFuaztzPVdhbGxlcjtn
+PVBhdWw7AB4AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAABAAAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAANAAAA
+DgAAAA8AAAAQAAAAEQAAABIAAAATAAAAFAAAABUAAAAWAAAAFwAAABgAAAAZAAAAGgAAABsA
+AAAcAAAAHQAAAB4AAAAfAAAAIAAAACEAAAAiAAAAIwAAACQAAAAlAAAAJgAAACcAAAAoAAAA
+KQAAACoAAAArAAAALAAAAC0AAAAuAAAALwAAADAAAAAxAAAAMgAAADMAAAA0AAAANQAAADYA
+AAA3AAAAOAAAADkAAAA6AAAAOwAAADwAAAA9AAAAPgAAAD8AAABAAAAAQQAAAEIAAABDAAAA
+RAAAAP7///9GAAAARwAAAEgAAABJAAAASgAAAEsAAABMAAAATQAAAE4AAABPAAAAUAAAAFEA
+AABSAAAAUwAAAFQAAAD+////VgAAAFcAAABYAAAAWQAAAFoAAABbAAAAXAAAAF0AAABeAAAA
+XwAAAGAAAABhAAAAYgAAAGMAAABkAAAAZQAAAGYAAABnAAAAaAAAAGkAAABqAAAAawAAAGwA
+AABtAAAAbgAAAG8AAABwAAAAcQAAAHIAAABzAAAAdAAAAHUAAAB2AAAAdwAAAHgAAAB5AAAA
+egAAAHsAAAB8AAAAfQAAAH4AAAB/AAAAgAAAAIEAAACCAAAA/v///4QAAACFAAAAhgAAAIcA
+AACIAAAAiQAAAIoAAAD+////jAAAAI0AAACOAAAAjwAAAJAAAACRAAAAkgAAAP7////9////
+/f///5YAAAD+/////v////7/////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+UgBvAG8AdAAgAEUAbgB0AHIAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAABYABQH//////////wMAAAAGCQIAAAAAAMAAAAAAAABGAAAAAAAAAAAAAAAA
+oIhg1E5zygGYAAAAgAAAAAAAAABEAGEAdABhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgACAf///////////////wAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEUAAAAcHwAAAAAAADEAVABhAGIAbABlAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAIB
+AQAAAAYAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVQAAAFNb
+AAAAAAAAVwBvAHIAZABEAG8AYwB1AG0AZQBuAHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAABoAAgECAAAABQAAAP////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAKogAAAAAAAAFAFMAdQBtAG0AYQByAHkASQBuAGYAbwByAG0A
+YQB0AGkAbwBuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAACAf///////////////wAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIMAAAAAEAAAAAAAAAUARABvAGMA
+dQBtAGUAbgB0AFMAdQBtAG0AYQByAHkASQBuAGYAbwByAG0AYQB0AGkAbwBuAAAAAAAAAAAA
+AAA4AAIBBAAAAP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+iwAAAAAQAAAAAAAAAQBDAG8AbQBwAE8AYgBqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAAgD///////////////8AAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////
+/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA
+AAD+////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////AQD+/wMKAAD/////BgkCAAAAAADAAAAAAAAARh8A
+AABNaWNyb3NvZnQgT2ZmaWNlIFdvcmQgRG9jdW1lbnQACgAAAE1TV29yZERvYwAQAAAAV29y
+ZC5Eb2N1bWVudC44APQ5snEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSAG8A
+bwB0ACAARQBuAHQAcgB5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAFgAFAf//////////AwAAAAYJAgAAAAAAwAAAAAAAAEYAAAAAAAAAAAAAAACwHIbi
+DHTKAaAAAADABwAAAAAAAEQAYQB0AGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAIB////////////////AAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQAAABwfAAAAAAAAMQBUAGEAYgBsAGUAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAgEBAAAA
+BgAAAP////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVAAAAU1sAAAAA
+AABXAG8AcgBkAEQAbwBjAHUAbQBlAG4AdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAGgACAQIAAAAFAAAA/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAqiAAAAAAAAIEAAACCAAAA/v///4QAAACFAAAAhgAAAIcAAACIAAAA
+iQAAAIoAAAD+///////////////////////////////////////////////9////////////
+//////////////////+fAAAA/f////7///+dAAAAngAAAP7////+////nAAAAP//////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////AQAAAP7/
+//8DAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAA
+EAAAABEAAAASAAAAEwAAABQAAAAVAAAAFgAAABcAAAAYAAAAGQAAABoAAAAbAAAAHAAAAB0A
+AAAeAAAA/v//////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//////////////////////////84AgAAAgAAAEACAAADAAAArAIAAAQAAADMAgAABQAAAOQC
+AAAGAAAAAAMAAAcAAAAoAwAACAAAAFQDAAAJAAAAfAMAAAoAAACYAwAACwAAAEwEAAAMAAAA
+WAQAAA0AAAB0BAAADgAAAJwEAAAPAAAAyAQAABAAAADwBAAAEQAAAAwFAAASAAAAwAUAABEA
+AAACAAAACQAAAEZ1bGxOYW1lAAMAAAAiAAAAUHJvdGVjdGl2ZSBNYXJraW5nIENsYXNzaWZp
+Y2F0aW9uAAQAAAAWAAAAQWRkaXRpb25hbCBEZXNjcmlwdG9yAAUAAAARAAAAQ3JlYXRvciBV
+c2VyTmFtZQAGAAAAEgAAAENyZWF0b3IgVXNlclRpdGxlAAcAAAATAAAAQ3JlYXRvciBPZmZp
+Y2VOYW1lAAgAAAATAAAAQ3JlYXRvciBEZXBhcnRtZW50AAkAAAAQAAAAQ3JlYXRvciBDb21w
+YW55AAoAAAAXAAAAQ3JlYXRvciBQcm94eUFkZHJlc3NlcwALAAAAEAAAAENyZWF0b3IgTWFu
+YWdlcgAMAAAACQAAAFVzZXJOYW1lAA0AAAAKAAAAVXNlclRpdGxlAA4AAAALAAAAT2ZmaWNl
+TmFtZQAPAAAACwAAAERlcGFydG1lbnQAEAAAAAgAAABDb21wYW55ABEAAAAPAAAAUHJveHlB
+ZGRyZXNzZXMAEgAAAAgAAABNYW5hZ2VyAAIAAAIAAADkBAAAHgAAAGQAAABDOlxEb2N1bWVu
+dHMgYW5kIFNldHRpbmdzXDgwNjVcTG9jYWwgU2V0dGluZ3NcVGVtcG9yYXJ5IEludGVybmV0
+IEZpbGVzXE9MS0M0XENPRyBNaW5zIDggOSAwNC5kb2MAAAAAHgAAABgAAABOT1QgUFJPVEVD
+VElWRUxZIE1BUktFRAAeAAAAEAAAAE5PIERFU0NSSVBUT1IAAAAeAAAAFAAAAFdhbGxlciwg
+UGF1bCA4MDY1AAAAHgAAACAAAABJbmZvcm1hdGlvbiBDb21wbGlhbmNlIE9mZmljZXIAAB4A
+AAAkAAAASUNVIC0gRGF0YSBQcm90ZWN0aW9uL0ZPSSAoQ0RCLU9TKQAAHgAAACAAAABDb3Jw
+b3JhdGUgRGV2ZWxvcG1lbnQgQnJhbmNoAAAAAB4AAAAUAAAASHVtYmVyc2lkZSBQb2xpY2UA
+AAAeAAAArAAAAENDTUFJTDpXYWxsZXIsIFBhdWwgYXQgTm9ydGhiYW5rfE1TOkhVTUJFUlNJ
+REUvTk9SVEhCQU5LL1BBVUxXQUxMRVJ8U01UUDp4eHh4Lnh4eHh4eEB4eHh4eHh4eHh4Lnh4
+eC54eHh4eHgueHh8WDQwMDpjPUdCO2E9IDtwPUh1bWJlcnNpZGUgUG9saWM7bz1Ob3J0aGJh
+bms7cz1XYWxsZXI7Zz1QYXVsOwAeAAAABAAAAAAAAAAeAAAAFAAAAFdhbGxlciwgUGF1bCA4
+MDY1AAAAHgAAACAAAABJbmZvcm1hdGlvbiBDb21wbGlhbmNlIE9mZmljZXIAAB4AAAAkAAAA
+SUNVIC0gRGF0YSBQcm90ZWN0aW9uL0ZPSSAoQ0RCLU9TKQAAHgAAACAAAABDb3Jwb3JhdGUg
+RGV2ZWxvcG1lbnQgQnJhbmNoAAAAAB4AAAAUAAAASHVtYmVyc2lkZSBQb2xpY2UAAAAeAAAA
+rAAAAENDTUFJTDpXYWxsZXIsIFBhdWwgYXQgTm9ydGhiYW5rfE1TOkhVTUJFUlNJREUvTk9S
+VEhCQU5LL1BBVUxXQUxMRVJ8U01UUDp4eHh4Lnh4eHh4eEB4eHh4eHh4eHh4Lnh4eC54eHh4
+eHgueHh8WDQwMDpjPUdCO2E9IDtwPUh1bWJlcnNpZGUgUG9saWM7bz1Ob3J0aGJhbms7cz1X
+YWxsZXI7Zz1QYXVsOwAeAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAFMAdQBtAG0A
+YQByAHkASQBuAGYAbwByAG0AYQB0AGkAbwBuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+KAACAf///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIMA
+AAAAEAAAAAAAAAUARABvAGMAdQBtAGUAbgB0AFMAdQBtAG0AYQByAHkASQBuAGYAbwByAG0A
+YQB0AGkAbwBuAAAAAAAAAAAAAAA4AAIBBAAAAP//////////AAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAgAAADgHAAAAAAAAAQBDAG8AbQBwAE8AYgBqAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAAgD/////////////
+//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAEA/v8DCgAA/////wYJAgAAAAAAwAAAAAAAAEYfAAAATWljcm9z
+b2Z0IE9mZmljZSBXb3JkIERvY3VtZW50AAoAAABNU1dvcmREb2MAEAAAAFdvcmQuRG9jdW1l
+bnQuOAD0ObJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v8AAAUBAgAAAAAAAAAAAAAA
+AAAAAAAAAgAAAALVzdWcLhsQk5cIACss+a5EAAAABdXN1ZwuGxCTlwgAKyz5rmwBAAAoAQAA
+DAAAAAEAAABoAAAADwAAAHAAAAAFAAAAjAAAAAYAAACUAAAAEQAAAJwAAAAXAAAApAAAAAsA
+AACsAAAAEAAAALQAAAATAAAAvAAAABYAAADEAAAADQAAAMwAAAAMAAAACQEAAAIAAADkBAAA
+HgAAABQAAABIdW1iZXJzaWRlIFBvbGljZQAAAAMAAAA2AAAAAwAAAA8AAAADAAAA3R0AAAMA
+AACoGQsACwAAAAAAAAALAAAAAAAAAAsAAAAAAAAACwAAAAAAAAAeEAAAAQAAADEAAABBY3Rp
+b25zIEFyaXNpbmcgRnJvbSBDaGllZiBPZmZpY2VyIEdyb3VwIE1lZXRpbmcADBAAAAIAAAAe
+AAAABgAAAFRpdGxlAAMAAAABAAAAAMwFAAATAAAAAAAAAKAAAAABAAAA9IICBZAGACABAAAV
+AAAAAwAgDhJSAQADACEOAAAAAAMA9w8AAAAAAgH5DwEAAAAQAAAAeHvM8/V7bEKrPQx4GUz2
+hB4AATABAAAAEgAAAEZPSSAwOS0wMjk3NmkuZG9jAAAAQAAHMIBUtdNOc8oBQAAIMFqgXAwN
+dMoBAgECNwEAAAAAAAAAHgADNwEAAAAFAAAALmRvYwAAAAADAAU3AQAAAB4ABzcBAAAAEgAA
+AEZPSSAwOS0wMjk3NmkuZG9jAAAAHgAINwEAAAABAAAAAAAAAAMACzf/////HgANNwEAAAAB
+AAAAAAAAAAMAFDcAAAAAAwD6fwAAAABAAPt/AEDdo1dFswxAAPx/AEDdo1dFswwDAP1/AAAA
+AAsA/n8AAAAACwD/fwAAAACNMwICkAYADgAAAAEA/////yAAIAAAAAAAPQQCEoADAA4AAADZ
+BwwAAgANAAAAHQADABsBAhOAAwAOAAAA2QcMAAMACwAlADQABABXAQIQgAEAFAAAAEZPSSAw
+OS0wMjk3NmlpaS5kb2MAOwUCEYAGALgNAAABAAkAAAPcBgAAAAAhBgAAAAAFAAAACQIAAAAA
+BQAAAAEC////AKUAAABBC8YAiAAgACAAAAAAACAAIAAAAAAAKAAAACAAAABAAAAAAQABAAAA
+AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////
+/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAABgAAAAYAA
+AAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAOAAAAH
+gAAAD/wAAB/8AAA//////yEGAABBC0YAZgAgACAAAAAAACAAIAAAAAAAKAAAACAAAAAgAAAA
+AQAYAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALeik498anhhTGpTPGZON2JKMmBI
+MGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBIMGBI
+MAAAAAAAAAAAAAAAAAAAAAAAAAAAALijlPro3d3LwtvDtdy7qty2oOGwluOxjuOxjuOxjuOw
+jOSuieWthuerg+iqgOmof+qle+yjd+2hc++fb++ea/GcaPGbZfKZY2BIMAAAAAAAAAAAAAAA
+AAAAAAAAAAAAALijlPrp3/rp3/rp3/rp3vno3fnn3fnn3Pnn2/jm2vnl2vjk2Pjk1/jj1fji
+1Pjh0vjg0ffe0PfezffczPfbyvfayPfZxvKaZWBIMAAAAAAAAAAAAAAAAAAAAAAAAAAAALmk
+lfrs4vnr4vrr4vnq4vnq4frp4Pnp4Pnp3vno3fnn3fnn2/nm2vjl2Pjk2Pji1vji1Pfg0vjf
+0fjez/fdzffcy/fayPGbZ2BIMAAAAAAAAAAAAAAAAAAAAAAAAAAAALmllvrt5frt5fnt5Prt
+5Prt5Prr4/rr4vrr4vrq4frp4Pro3/ro3fno3Pnm2vnl2fjk2Pjj1fji0/jg0fffz/jezvjd
+zPCcamBIMAAAAAAAAAAAAAAAAAAAAAAAAAAAALqmlvrv6Prv5/rv6Pru5/ru5/ru5vvt5vrt
+5frs5Prs4/rr4vrq4frq39GnidCmh8+khc+jhM6igs2hgfjh0/jf0Pjezu+ebWBIMAAAAAAA
+AAAAAAAAAAAAAAAAAAAAALunmPvx6/vx6/vx6vvw6vvx6fvw6frw6Pvv6Pvv5/ru5vrt5frs
+5Prr4vrr4frp3/no3fnn2/nm2vjk1/ji1fjh1Pjg0e6gcGBIMAAAAAAAAAAAAAAAAAAAAAAA
+AAAAALyomfvz7fvy7eG/puC9o9+7od25n9y3nNu1mtmzmNixldevk9aukdWsj9SqjdOpi9Kn
+idCmh9Ckhs+jhPnl2Pjj1vjh1O2hc2BIMAAAAAAAAAAAAAAAAAAAAAAAAAAAAL2pmvz07/v0
+7/v07/v07/v07/zz7vvz7vvz7vvy7fvx7Pvx6/vw6vrv6Pru5/rt5frs4/rr4vrp4Pro3fnm
+2/nl2Pjj1uujd2BIMAAAAAAAAAAAAAAAAAAAAAAAAAAAAL6qnPv28fv18eTDq+PBqeG/puC9
+pN+7od25n9y3ndu1mtq0mNiyltewlNaukdWsj9SrjdOpi9KoidGmiPno3vnn2/nl2eule2BI
+MAAAAAAAAAAAAAAAAAAAAAAAAAAAAL+snPz38/z38/z38/z28/z28/z28/z28/v18vz18fz1
+8fv18Pv07/vz7vvy7Pvx6/rw6frv5/rt5frs4vnq4Pno3vnm2+mnf2BIMAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAMCsnfz49fz49ebHr+XFreTDq+PBqeHApuC+pN+8ot66n9y4ndu2mtq0mNiy
+ltewlNauktWsj9SrjdOpjPrs4/rq4fno3eepgWBIMAAAAAAAAKxgN6lcMalcMaJZK6JZK51S
+KJhLIJhLII1EFY1EFY1EFYY7B4Y7B4A1BIA1BHcxAPz39Pz28/z28vz18fv07/vz7vvy6/vw
+6frv5/rt5frr4vrq4OashWBIMAAAAAAAALdpQ+SpkeSpkeSpkeSpkeejh9uiiduiidiagdaV
+e9aVe8eOdMeOdMeOdMeOdIA1BN+8ot66oNy4ndu2m9q0mdmyltewlNauktWtkPrv5/vt5frr
+4uStiWBIMAAAAAAAALdpQ+uvl/r18/jy8Pjw7vjw7vbu7Pft6vbs6Pbq5/bo5vXn5PXm4vXm
+4seOdIA1BPz59/z59/z49vz39Pz28/v18fv07/vy7fvx6/vw6fvu5vrs5OOvjWBIMAAAAAAA
+AL9uSuuvl/r18/rz8vrz8vjw7vjw7vbu7Pft6vbs6Pbq5/bo5vXn5PXm4seOdIY7B+LAp+C+
+pd+8ot66oN24ntu2m9q0mdmyltixlPvx6/rv6Pru5uOxkGBIMAAAAAAAAL9uSu61nvv29adQ
+JohLJs64q/jw7vjw7qdQJlU7FbOhkPbq5/bo5vXn5MeOdIY7B/37+vz6+fz6+Pz59/z49vz3
+9Pz28vv18fv07/vy7Pvw6/vv6OKzlGBIMAAAAAAAAMRzUe61nvv498F8Xtd2R4hLJuzm4/bu
+7MxoO4hLJlU7Feja1Pbq5/Xq5taVe41EFf37+/37+syfgMyfgMyfgMyfgMyfgMyfgMyfgPzz
+7vvy7Pvw6uC1mGBIMAAAAAAAAMx6VO+8qPv498WBaueUdNd2R4hLJuDQx9WAWdd2R4hLJpqI
+dvbs6Pbq59aVe41EFf38+/37+vHIqOS8oOXBpeW9ouCxkeK0lcyfgPz07/vy7fvx69+3mmBI
+MAAAAAAAAM99V++8qPz5+NCJcbF9Ztiagdd2R4hLJr9uSsWBarJiOFU7Feja1Pbs6NiagY1E
+Ff38/P38+/HIqNenht2wkOO3md6vkOK1l8yfgPz08Pv07vvx7N+4nWBIMAAAAAAAANOBX++8
+qPz6+daVe7F9Zui6p8WBatd2R4hLJuSpkcpzSIhLJpqIdvft6tiagZhLIP38/P38+/HIqOe/
+o+bBpuS8oNytjOG2l8yfgPz18fv07/vz7d25n2BIMAAAAAAAANmGZfLGtf38+9uiibF9ZvPk
+39uiieSpkYhLJuDQx926rbJiOFU7Ffbu7NuiiZhLIP38/P38+/HIqPLh1erWyPnz7uLAqOG/
+peC9o9y5o9y5o9y5o926oWBIMAAAAAAAAN6KaPLGtdaVe+SpkeSpkbF9Zv39/c2DZ41RL+DQ
+x+SpkcWBarJiONiagduiiZ1SKP38/P38+/HIqPv39Pz7++nVx7yqm4p2Y4ZyXoBrV3pjTnJb
+RWpTPGBIMAAAAAAAAN6KaPLGtdaVe9CJcdCJccRzUd26rfz5+JZZOfv29ch2Ucl7XMl7XL9u
+Suejh6JZK/38/P38+/HIqP39/fny7fLh1b2rm+rQvejMueXIs+LCrN+8p2BIMCUlJAAAAAAA
+AOSPbvLGtf39/f39/f38+/38+/z7+vz6+fz5+Pv39vv29fr18/rz8vjy8OSpkaJZK/38/P38
+/PHIqPHIqPHIqPHIqL+snf3s4vnm2vLczuzSwWBIMCUlJBgYGAAAAAAAAOeUdPLGtf39/f39
+/f39/f39/P38+/z7+vz6+fz49/v49/v29fr18/rz8uSpkalcMf38/P38/P38+/z8+/37+v37
++cCunv3s4vnm2vPczmVONiUlJBgYGAAAAAAAAAAAAOeUdPLGtfLGtfLGtfLGtfLGtfLGte+8
+qO+8qO+8qO61nu61nuuvl+uvl+SpkalcMf38/P38/P38+/37+/37+v37+cKwof3s4vnm2mxV
+PiUlJBgYGAAAAAAAAAAAAAAAAOeUdOeUdOeUdOSPbuSPbt6KaNmGZdOBX9WAWc99V8h2UcRz
+Ub9uSr9uSrdpQ6xgN/38/P38/P38/P38+/37+/37+sSyov3s4oFuWSUlJBgYGAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAM+9r/39/f39/f39/f39/f39/f39/f39/f39/f39/f39
+/f38/P39/P38/P38/P37+/37+sazo5qEdCUlJBgYGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAM+9r8+9r8+9r8+9r868rs27rc27rc26rMu5q8u5qsu3qsq3qcm2qMi1p8i1
+pse0pca0pce0pCUlJBgXFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAOM5Ag+ABgAA/gAA0M8R4KGxGuEAAAAAAAAA
+AAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAAdgAAAAAAAAAAEAAAeAAAAAEAAAD+
+////AAAAAHcAAAD/////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+///////////////spcEAA2AJBAAA8BK/AAAAAAAAEAAAAAAABgAAYzkAAA4AYmpiastzy3MA
+AAAAAAAAAAAAAAAAAAAAAAAJBBYAKlwAAKkZAQCpGQEApzAAAAAAAAC7AAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAD//w8AAAAAAAAAAAD//w8AAAAAAAAAAAD//w8AAAAAAAAAAAAAAAAAAAAA
+AKQAAAAAAOQHAAAAAAAA5AcAAOQHAAAAAAAA5AcAAAAAAADkBwAAAAAAAOQHAAAAAAAA5AcA
+ABQAAAAAAAAAAAAAAPgHAAAAAAAAYB4AAAAAAABgHgAAAAAAAGAeAAA4AAAAmB4AADwAAADU
+HgAATAAAAPgHAAAAAAAAOU0AAGgBAAAsHwAAAAAAACwfAAAAAAAALB8AAAAAAAAsHwAAAAAA
+ACwfAAAAAAAALB8AAAAAAAAsHwAAAAAAACwfAAAAAAAAuEwAAAIAAAC6TAAAAAAAALpMAAAA
+AAAAukwAAAAAAAC6TAAAAAAAALpMAAAAAAAAukwAACQAAAChTgAAaAIAAAlRAADQAAAA3kwA
+ABUAAAAAAAAAAAAAAAAAAAAAAAAA5AcAAAAAAABfIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAs
+HwAAAAAAACwfAAAAAAAAXyEAAAAAAABfIQAAAAAAAN5MAAAAAAAAAAAAAAAAAADkBwAAAAAA
+AOQHAAAAAAAALB8AAAAAAAAAAAAAAAAAACwfAAAAAAAA80wAABYAAABLJAAAAAAAAEskAAAA
+AAAASyQAAAAAAABfIQAAlAAAAOQHAAAAAAAALB8AAAAAAADkBwAAAAAAACwfAAAAAAAAuEwA
+AAAAAAAAAAAAAAAAAEskAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAXyEAAAAAAAC4TAAAAAAAAAAAAAAAAAAASyQAAAAAAABLJAAAUgEA
+ACBDAAD0AAAA5AcAAAAAAADkBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEUAAAAAAAAsHwAAAAAAACAfAAAMAAAAkHD0
+bE9zygEAAAAAAAAAAGAeAAAAAAAA8yEAAMQAAAAURAAAHgAAAAAAAAAAAAAAbEwAAEwAAAAJ
+TQAAMAAAADlNAAAAAAAAMkQAANIAAADZUQAAAAAAALciAAAMAQAA2VEAADwAAAAERQAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAERQAAFAAAANlRAAAAAAAAAAAAAAAAAADkBwAAAAAAABhFAABUBwAALB8A
+AIQAAACwHwAAXgAAAEskAAAAAAAADiAAAEwAAABaIAAABQEAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAALB8AAAAAAAAsHwAAAAAAACwfAAAAAAAA3kwAAAAAAADeTAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwyMAAIgAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAACwfAAAAAAAALB8AAAAAAAAsHwAAAAAAADlNAAAAAAAAXyEA
+AAAAAABfIQAAAAAAAF8hAAAAAAAAXyEAAAAAAAAAAAAAAAAAAPgHAAAAAAAA+AcAAAAAAAD4
+BwAAxBQAALwcAACkAQAA+AcAAAAAAAD4BwAAAAAAAPgHAAAAAAAAvBwAAAAAAAD4BwAAAAAA
+APgHAAAAAAAA+AcAAAAAAADkBwAAAAAAAOQHAAAAAAAA5AcAAAAAAADkBwAAAAAAAOQHAAAA
+AAAA5AcAAAAAAAD/////AAAAAAIADAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAE1pbnV0ZXMgb2YgQ2hpZWYgT2ZmaWNlciBHcm91cCBNZWV0aW5nDTA5MzAgaG91
+cnMsIFdlZG5lc2RheSAxNyBBdWd1c3QgMjAwNQ1DaGllZiBPZmZpY2Vyc5IgQ29uZmVyZW5j
+ZSBSb29tDQ1PcGVuIFNlc3Npb24NDTEuCUF0dGVuZGFuY2UNDQlDaGllZiBDb25zdGFibGUg
+KENoYWlyKSwgQUNDKE8pLCBBL0FDQyhPUykgYW5kIENoaWVmIFN1cGVyaW50ZW5kZW50IENo
+ZWVzZW1hbi4NCShBQ08oUykgZm9yIENsb3NlZCBpdGVtcyBvbmx5KQ0NMi4JQXBvbG9naWVz
+DQ0yLjEJQS9EZXB1dHkgQ2hpZWYgQ29uc3RhYmxlIGFuZCBBQ08oUykuDQ0zLglNaW51dGVz
+IG9mIHRoZSBwcmV2aW91cyBtZWV0aW5nDQ0zLjEuCVRoZSBtaW51dGVzIG9mIFdlZG5lc2Rh
+eSAxMCBBdWd1c3QgMjAwNSB3ZXJlIGFncmVlZC4NDTQuCU91dHN0YW5kaW5nIEFjdGlvbnMg
+U2NoZWR1bGUNDTQuMQlBbGwgcmVsZXZhbnQgYWN0aW9ucyB3ZXJlIHVwZGF0ZWQgYXMgcGVy
+IHRoZSBzY2hlZHVsZS4NDTUuCVRyaWJ1bmUgVHJ1c3QgRXZlbnQNDTUuMQlUaGlzIG1hdHRl
+ciBoYWQgbm93IGJlZW4gcmVzb2x2ZWQgb3V0c2lkZSB0aGUgQ2hpZWYgT2ZmaWNlciBHcm91
+cCBtZWV0aW5nIGFuZCANCU1yIEZlbnRvbiB3YXMgcHJvZ3Jlc3NpbmcuDQ02LglNZW1iZXJz
+aGlwIG9mIFVuYWNjZXB0YWJsZSBPcmdhbmlzYXRpb25zDQ02LjEJQSBwYXBlciBwcmVwYXJl
+ZCBieSB0aGUgSGVhZCBvZiBQZXJzb25uZWwgQnJhbmNoLCBvdXRsaW5pbmcgdGhlIGN1cnJl
+bnQgbmF0aW9uYWwgcG9zaXRpb24gaW4gcmVzcGVjdCBvZiBtZW1iZXJzaGlwIG9mIHVuYWNj
+ZXB0YWJsZSBvcmdhbmlzYXRpb25zIHdhcyBjb25zaWRlcmVkLiAgVGhlIHBhcGVyIG1hZGUg
+dGhyZWUgcmVjb21tZW5kYXRpb25zIGluIHJlc3BlY3Qgb2YgcmVtaW5kaW5nIHBvbGljZSBv
+ZmZpY2Vycywgc3BlY2lhbCBjb25zdGFibGVzIGFuZCBzdXBwb3J0IHN0YWZmIGFib3V0IHRo
+ZWlyIG9ibGlnYXRpb25zIGFuZCByZXNwb25zaWJpbGl0aWVzIGluIHJlc3BlY3Qgb2YgdW5h
+Y2NlcHRhYmxlIG9yZ2FuaXNhdGlvbnMsIHN1Y2ggYXMgQk5QLCBDb21iYXQgMTggYW5kIHRo
+ZSBOYXRpb25hbCBGcm9udC4gIFRoZSByZWNvbW1lbmRhdGlvbnMgd2VyZSBhZ3JlZWQsIGJ1
+dCBpdCB3YXMgZGV0ZXJtaW5lZCB0aGF0IGNvbW11bmljYXRpb24gd291bGQgYmUgYnkgaW5j
+bHVzaW9uIGluIEZvcmNlIGFuZCBBc3NvY2lhdGlvbiBwdWJsaWNhdGlvbnMgcmF0aGVyIHRo
+YW4gYnkgZW1haWwgb3IgbGV0dGVyLg0NCUFDVElPTjogIE1ycyBNb3JsZXkgdG8gcmVtaW5k
+IHN0YWZmIG9mIHRoZWlyIG9ibGlnYXRpb25zIGJ5IHB1Ymxpc2hpbmcgYXBwcm9wcmlhdGUg
+Z3VpZGFuY2UgaW4gRm9yY2UgYW5kIFN0YWZmIEFzc29jaWF0aW9uIHB1YmxpY2F0aW9ucy4g
+IENvbnRhY3QgdG8gYmUgbWFkZSB3aXRoIE1hcmtldGluZyBhbmQgUGxhbm5pbmcgU2VjdGlv
+biB0byBtYWtlIHRoZSBuZWNlc3NhcnkgYXJyYW5nZW1lbnRzLg0NNy4JUm9hZHMgUG9saWNp
+bmcgUmVhbGl0eSBDaGVjaw0NNy4xCUEvQUNDKE9TKSBwcmVzZW50ZWQgYSByZXBvcnQgYnkg
+Q2hpZWYgSW5zcGVjdG9yIFN0ZWFkIGluIHRoZSBPcGVyYXRpb25zIEJyYW5jaCBvbiB0aGUg
+c3ViamVjdCBvZiByb2FkcyBwb2xpY2luZyByZWFsaXR5IGNoZWNrcy4gIFRoZSBwYXBlciBv
+dXRsaW5lZCBwcm9ncmVzcyBvZiB0aGUgQmVzdCBWYWx1ZSBSZXZpZXcgYW5kIHRoZSByZXN1
+bHRzIG9mIHJlYWxpdHkgY2hlY2tzIGNhcnJpZWQgb3V0IGJ5IFBvbGljZSBBdXRob3JpdHkg
+TWVtYmVycyBpbiBBIGFuZCBDIERpdmlzaW9uLiAgVGhlIHBhcGVyIHJlYWNoZWQgYSBudW1i
+ZXIgb2YgY29uY2x1c2lvbnMgaW4gcmVzcGVjdCBvZiByb2FkcyBwb2xpY2luZyBtYXR0ZXJz
+LiAgQWZ0ZXIgZGlzY3Vzc2lvbiwgaXQgd2FzIGFncmVlZCBhcyBmb2xsb3dzOg0NCQ0NCUFD
+VElPTjoNDTEuCUEvQUNDKE9TKSB0byBhcnJhbmdlIGZvciBhIHByZXNlbnRhdGlvbiB0byBi
+ZSBicm91Z2h0IHRvIHRoZSBuZXh0IFN0cmF0ZWd5IFRlYW0gdG8gcHJvdmlkZSBhIHJldmll
+dyBvZiByb2FkcyBwb2xpY2luZyBtYXR0ZXJzLCB3aGljaCB3aWxsIGJlIGZhY3R1YWwgYW5k
+IHRvdWNoIG9uIGlzc3VlcyBpbmNsdWRpbmcgaG93IHRoZSBjb3Jwb3JhdGUgc3RhbmRhcmRz
+IGFyZSBtYWludGFpbmVkLiAgSW5jbHVkZWQgd2l0aGluIHRoZSBwcmVzZW50YXRpb24gc2hv
+dWxkIGJlIGNvbnNpZGVyYXRpb24gb2YgVFBBQyBhbmQgcHVyc3VpdCBtYW5hZ2VtZW50IGlz
+c3Vlcy4JDQ04LglPcCBCcmFja25lbGwgliBUc3VuYW1pIENvc3QgUmVjb3ZlcnkNDTguMQlB
+Q0MoTykgc3Bva2UgdG8gYSBwYXBlciBwcmVwYXJlZCBieSBNciBOZXdzdGVhZCwgRmluYW5j
+ZSBhbmQgUmVzb3VyY2VzIE1hbmFnZXIsIENyaW1lIE1hbmFnZW1lbnQvT3BzIEJyYW5jaCwg
+d2hpY2ggdXBkYXRlZCBDaGllZiBPZmZpY2VycyBvbiB0aGUgZmluYW5jaWFsIGltcGxpY2F0
+aW9ucyBhcmlzaW5nIG91dCBvZiBIdW1iZXJzaWRlIFBvbGljZZJzIHN1cHBvcnQgdG8gdGhl
+IHJlY2VudCBtYWpvciBkaXNhc3RlciBpbiB0aGUgRmFyIEVhc3QuICBUaGUgcGFwZXIgbWFk
+ZSBwcm9wb3NhbHMgZm9yIGhvdyBzdGFmZiBjb3N0cyB0aGF0IGhhZCBiZWVuIHJlY292ZXJl
+ZCBieSB0aGUgRm9yY2Ugc2hvdWxkIGJlIHJlZGlzdHJpYnV0ZWQuICBPbmUgb2YgdGhlc2Ug
+b3B0aW9ucyBpbmNsdWRlZCByZXRhaW5pbmcgdGhlIG1vbmV5IGF0IHRoZSBjZW50cmUgYXMg
+cGFydCBvZiB0aGUgQ2hpZWYgQ29uc3RhYmxlknMgUGVyZm9ybWFuY2UgRnVuZC4gIEl0IHdh
+cyBkZXRlcm1pbmVkLCBob3dldmVyLCB0aGF0IHRoZSBmdWxsIGJlbmVmaXQgb2YgZnVuZGlu
+ZyBzaG91bGQgZ28gYmFjayB0byB0aGUgRGl2aXNpb25zIGFuZCBCcmFuY2hlcyB0aGF0IGhh
+ZCBjb250cmlidXRlZCB0aGUgc3RhZmYuDQ0JQUNUSU9OOiAgTXIgTmV3c3RlYWQgdG8gZW5z
+dXJlIHRoYXQgZnVuZGluZyByZXR1cm5lZCB0byB0aGUgRm9yY2UgaW4gcmVzcGVjdCBvZiBz
+dGFmZiBzaG91bGQgdGhlbiBiZSByZXR1cm5lZCB0byB0aGUgcmVzcGVjdGl2ZSBkaXZpc2lv
+bmFsIG9yIGJyYW5jaCBwb2xpY2UgYW5kIHBvbGljZSBzdGFmZiBwYXkgbGluZS4NDTkuCUJv
+bnVzIFBheW1lbnRzDQ05LjEJQUNDKE8pIHNwb2tlIHRvIGEgcGFwZXIgb3V0bGluaW5nIGhv
+dyBmdXR1cmUgY2xhaW1zIGZvciBib251cyBwYXltZW50cyBmb3IgSW5zcGVjdG9ycyBhbmQg
+Q2hpZWYgSW5zcGVjdG9ycyBzZXJ2aW5nIG9uIG5hdGlvbmFsIG9wZXJhdGlvbnMgb3V0IG9m
+IGZvcmNlIHNob3VsZCBiZSByZXNvbHZlZC4gIFRoZSBwYXBlciBwcm9wb3NlZCB0aHJlZSBk
+cmFmdCBjcml0ZXJpYSB0aGF0IHNob3VsZCBiZSBjb25zaWRlcmVkIGluIHRoZSBmdXR1cmUu
+ICBDaGllZiBPZmZpY2VycyBhcHByb3ZlZCB0aGUgaW50cm9kdWN0aW9uIG9mIHRoZSBjcml0
+ZXJpYSwgc3ViamVjdCB0byBzb21lIG1pbm9yIGFtZW5kbWVudHMgd2hpY2ggd291bGQgYmUg
+aW5jb3Jwb3JhdGVkIGluIHRoZSBwYXBlciB3aGVuIGRpc3RyaWJ1dGVkIHdpdGggdGhlIG1p
+bnV0ZXMuICANDQlBQ1RJT046ICBUaGUgcGFwZXIgdG8gYmUgY2lyY3VsYXRlZCB2aWEgdGhl
+IENPRyBtaW51dGVzIGFuZCBhZG9wdGVkIGZvcnRod2l0aC4gIFRoZSBwYXltZW50IHRvIG9m
+ZmljZXIgUEgsIHdobyBpcyBjdXJyZW50bHkgc2Vjb25kZWQgb3V0IG9mIHRoZSBGb3JjZSwg
+c2hvdWxkIGJlIGFwcHJvdmVkIJYgQUNDKE8pDQ0xMC4JRm9vdGJhbGwgQ29tbWl0bWVudHMN
+DTEwLjEJQUNDKE8pIGJyaWVmZWQgdGhlIENoaWVmIE9mZmljZXJzIG9uIHNvbWUgb2YgdGhl
+IGFycmFuZ2VtZW50cyBmb3IgdGhlIGZvcnRoY29taW5nIGZvb3RiYWxsIHNlYXNvbi4gIEhl
+IGV4cGxhaW5lZCB0aGF0IGEgZGVjaXNpb24gaGFkIGJlZW4gbWFkZSB0byBpbmNyZWFzZSBQ
+U1UgdHJhaW5pbmcsIGJ1dCB1bnRpbCBzdWNoIHRpbWUgYXMgYWxsIHN0YWZmIHdlcmUgaW4g
+cGxhY2UsIHRoZXJlIHdvdWxkIHN0aWxsIGJlIGEgbmVlZCB0byB1c2Ugc3RhZmYgZnJvbSBh
+Y3Jvc3MgdGhlIEZvcmNlIHRvIHN1cHBvcnQgcG9saWNpbmcgb3BlcmF0aW9ucy4gIEhlIGV4
+cGxhaW5lZCB0aGF0IG9uZSBvZiB0aGUgY29uc2lkZXJhdGlvbnMgd2FzIHRoZSBuZWVkIHRv
+IHBheSBtb25leSB0byBzb21lIG9mZmljZXJzIHRvIHJlY29tcGVuc2UgdGhlbSB3aGVuIHRo
+ZXkgYXJlIHJlZ3VsYXJseSBoYXZpbmcgcmVzdCBkYXlzIGNhbmNlbGxlZCBhbmQgdGhpcyB3
+b3VsZCBzaG93IGdvb2Qgd2lsbCwgYXMgd2VsbCBhcyBoZWxwaW5nIHRvIHJlbGlldmUgYnVk
+Z2V0YXJ5IHByZXNzdXJlcyBvbiBkaXZpc2lvbnMuICBUaGUgb3RoZXIgaXNzdWUgd2FzIHRo
+YXQgdGhlIEZvcmNlIHdhcyBpbXByb3Zpbmcgb24gaXRzIGNvc3QgcmVjb3ZlcnkgZnJvbSBj
+bHVicywgaG93ZXZlciwgQyBEaXZpc2lvbiBpbiBwYXJ0aWN1bGFyLCBkaWQgbm90IGhhdmUg
+YSBwcm9mZXNzaW9uYWwgZm9vdGJhbGwgdGVhbSBhbmQgdGhlcmVmb3JlLCBhbHRob3VnaCBz
+dXBwbHlpbmcgc3RhZmYgdG8gb3RoZXIgZGl2aXNpb25zLCB3ZXJlIG5vdCBhYmxlIHRvIGJl
+bmVmaXQgZnJvbSB0aGUgaW5jb21lIGJlaW5nIHJlY292ZXJlZC4gIFRoZSBmb2xsb3dpbmcg
+ZGVjaXNpb25zIHdlcmUgcmVhY2hlZC4gIA0NCUFDVElPTlM6ICANDQkxLglUaGUgQ2hpZWYg
+Q29uc3RhYmxlIHJlcXVlc3RlZCB0aGF0IEFDQyhPKSBleHBsb3JlIGZ1cnRoZXIgdGhlIG9w
+dGlvbiBvZiByZWNvbXBlbnNpbmcgb2ZmaWNlcnMgaW4gcmVzcGVjdCBvZiByZWd1bGFyIGNh
+bmNlbGxlZCByZXN0IGRheXMuDQ0JMi4JQUNDKE8pIHRvIGNsYXJpZnkgdG8gd2hpY2ggYWNj
+b3VudCByZXR1cm5lZCBpbmNvbWUgY3VycmVudGx5IGdvZXMgYW5kIHRoZW4gdGFrZSB0aGUg
+bWF0dGVyIHRvIFN0cmF0ZWd5IFRlYW0gaW4gcmVzcGVjdCBvZiBkZXRlcm1pbmluZyB3aGV0
+aGVyIGZ1bmRpbmcgc2hvdWxkIGJlIHJldHVybmVkIHRvIGEgY2VudHJhbCBidWRnZXQgYW5k
+IHNoYXJlZCBiZXR3ZWVuIHRoZSBmb3VyIGRpdmlzaW9ucy4NDTExLglDaGllZiBPZmZpY2Vy
+cyBJVA0NMTEuMQlDaGllZiBTdXBlcmludGVuZGVudCBDaGVlc2VtYW4gZGlzY3Vzc2VkIENo
+aWVmIE9mZmljZXJzkiBmdXR1cmUgSVQgYXJyYW5nZW1lbnRzIGluIHJlc3BlY3Qgb2YgZWxl
+Y3Ryb25pYyBkaWFyaWVzIGFuZCBlbWFpbCBhY2Nlc3MgYW5kIGhlIHdhcyByZXF1ZXN0ZWQg
+dG8gZXhwbG9yZSB0aGUgb3B0aW9ucyBhbmQgYnJpbmcgZnVydGhlciBwcm9wb3NhbHMgYmFj
+ayB0byBDT0cgYXQgYSBmdXR1cmUgZGF0ZS4NDQlBQ1RJT046ICBDaGllZiBTdXBlcmludGVu
+ZGVudCBDaGVlc2VtYW4gdG8gZXhwbG9yZSBvcHRpb25zIGFuZCBicmluZyBwcm9wb3NhbHMg
+YmFjayB0byBDT0cuDQ0xMi4JUmVwb3J0IEJhY2sgb24gQ2hpZWYgT2ZmaWNlciBWaXNpdHMN
+DTEyLjEJVGhlIENoaWVmIENvbnN0YWJsZSBhbmQgQUNDKE8pIGhhZCByZWNlbnRseSB2aXNp
+dGVkIHRoZSBJbmNpZGVudCBSb29tIGluIHJlc3BlY3Qgb2YgdGhlIG11cmRlciBpbiBIdWxs
+IGxhc3Qgd2Vla2VuZC4gVGhlIENoaWVmIHdpc2hlZCB0byBwdXQgb24gcmVjb3JkIGhpcyBj
+b25ncmF0dWxhdGlvbnMgdG8gdGhlIHRlYW0gcmVzcG9uc2libGUgZm9yIGVuc3VyaW5nIHRo
+ZSBhcnJlc3Qgb2YgdGhlIHN1c3BlY3QgcHJpb3IgdG8gaGlzIGRlcGFydGluZyBmcm9tIHRo
+ZSBVSy4gDQ0xMy4JQ29tbXVuaWNhdGlvbiBJc3N1ZXMNDTEzLjEJVGhlcmUgd2VyZSBubyBj
+b21tdW5pY2F0aW9uIGlzc3Vlcy4NDTE0LglTdHJhdGVneSBUZWFtL1BBIE1lZXRpbmcgliBM
+YXN0IFdlZWsvVGhpcyBXZWVrL05leHQgV2Vlaw0NMTQuMQlUaGVyZSB3ZXJlIG5vIGlzc3Vl
+cyB0byByZXBvcnQuDQ1DbG9zZWQgU2Vzc2lvbg0NMTUuCUNhbGwgSGFuZGxpbmcNDTE1LjEJ
+SGF2aW5nIHJldHVybmVkIGZyb20gbGVhdmUsIHRoZSBDaGllZiBDb25zdGFibGUgcmVxdWVz
+dGVkIGFuIHVwZGF0ZSBvbiBhY3Rpb24gdGFrZW4gZm9sbG93aW5nIHRoZSBQb2xpY2UgQXV0
+aG9yaXR5IHJlcG9ydCBpbiByZXNwZWN0IG9mIGNvbmNlcm5zIG92ZXIgaW5jaWRlbnQgaGFu
+ZGxpbmcgaXNzdWVzLiAgVGhpcyBpbnZlc3RpZ2F0aW9uIHdhcyBiZWluZyBvdmVyc2VlbiBi
+eSBBL0FDQyhPUykuICBUaGUgQ2hpZWYgQ29uc3RhYmxlIHJlLXN0YXRlZCB0aGF0IHRoaXMg
+aXMgYSBtYXR0ZXIgb2YgcHJpb3JpdHkgZm9yIHRoZSBGb3JjZSBhbmQgdGhlIFBvbGljZSBB
+dXRob3JpdHkuICBDaGllZiBTdXBlcmludGVuZGVudCBDaGVlc2VtYW4sIHdobyBoYWQgYmVl
+biBzZWNvbmRlZCB0byBjYXJyeSBvdXQgd29yayB1bnRpbCBDaGllZiBTdXBlcmludGVuZGVu
+dCBEYXZpc29uIHJldHVybmVkLCBvdXRsaW5lZCB0byBDaGllZiBPZmZpY2VycyBwcm9ncmVz
+cyBtYWRlIGR1cmluZyB0aGUgbGFzdCB3ZWVrIGluIHByb2R1Y2luZyBhIGNvbXByZWhlbnNp
+dmUgcmVjb3ZlcnkgcGxhbiBhbmQgdGhlIGdvdmVybmFuY2UgYXJyYW5nZW1lbnRzIHRoYXQg
+d291bGQgdW5kZXJwaW4gdGhlIGRlbGl2ZXJ5IG9mIHRoaXMgcGxhbiBhbmQgZW5zdXJlIHRo
+YXQgdGhlIGlzc3VlcyBvZiBjb25jZXJuIHdlcmUgYWRkcmVzc2VkIGFzIHNvb24gYXMgcG9z
+c2libGUuDQ0xNS4yCUhhdmluZyBsaXN0ZW5lZCB0byB0aGUgdmVyYmFsIHByZXNlbnRhdGlv
+biwgdGhlIENoaWVmIENvbnN0YWJsZSBzYWlkIGhlIHdhcyBzYXRpc2ZpZWQgdGhhdCB3aGls
+c3QgdGhpcyBpcyBhIG1hdHRlciBvZiBwcmlvcml0eSBmb3IgdGhlIFBvbGljZSBBdXRob3Jp
+dHkgYW5kIHRoZSBGb3JjZSwgYSBnb29kIGRlYWwgb2Ygd29yayBpcyBhbHJlYWR5IGluIGhh
+bmQgYW5kIGEgZm9ybWFsIHVwZGF0ZSB3aWxsIHRoZW4gYmUgcHJvdmlkZWQgdG8gdGhlIFBv
+bGljZSBBdXRob3JpdHkgb24NCTIwIFNlcHRlbWJlci4NDTE2LglDaGllZiBDb25zdGFibGWS
+cyBQZXJmb3JtYW5jZSBGdW5kDQ0xNi4xCUNoaWVmIE9mZmljZXJzIG5vdGVkIHRoYXQgozQs
+MDAwIGhhZCBiZWVuIGFsbG9jYXRlZCBmcm9tIHRoZSBmdW5kIGluIHJlc3BlY3Qgb2YgRmVz
+dHMgYW5kIKM2MTAgaW4gcmVzcGVjdCBvZiBmYWl0aCBjYWxlbmRhcnMuCQ0JDQ0NMTcuCUJy
+b256ZSBDb21tYW5kZXJzIJYgRmlyZWFybXMNDTE3LjEJQS9BQ0MoT1MpIHByZXNlbnRlZCBh
+IHJlcG9ydCBwcmVwYXJlZCBieSBTdXBlcmludGVuZGVudCBEdWdnbGVieSBpbiByZXNwZWN0
+IG9mIHRoaXMgbWF0dGVyLiAgQWZ0ZXIgZGlzY3Vzc2lvbiBhcm91bmQgYSBudW1iZXIgb2Yg
+aXNzdWVzIGFyaXNpbmcgZnJvbSB0aGUgcGFwZXIgYW5kIGluIHJlc3BlY3Qgb2Ygb3RoZXIg
+ZmlyZWFybXMgbWF0dGVycywgaXQgd2FzIGFncmVlZCB0aGF0IGEgbW9yZSBkZXRhaWxlZCBh
+bmFseXNpcyBvZiBpbmNpZGVudHMgaW5jbHVkaW5nIHRocmVhdCBhc3Nlc3NtZW50cywgdGhl
+IHRpbWUgdGFrZW4gdG8gcmVzb2x2ZSB0aGVtIGFuZCB0aGUgdWx0aW1hdGUgb3V0Y29tZSwg
+d2FzIG5vdyByZXF1aXJlZC4gIFRoaXMgc2hvdWxkIGZlZWQgaW50byBhIHBhcGVyIHRoYXQg
+d2FzIGFscmVhZHkgYmVpbmcgcHJlcGFyZWQgaW4gcmVzcGVjdCBvZiBHb2xkIENvbW1hbmQg
+YW5kIHRoZSBDaGllZiBDb25zdGFibGUgc3RhdGVkIHRoYXQgaGUgd291bGQgbGlrZSBhIGhh
+bGYgZGF5IENoaWVmIE9mZmljZXIgQXdheSBEYXkgc2V0IGFzaWRlIHRvIHJlc29sdmUgYSBu
+dW1iZXIgb2YgZmlyZWFybXMgaXNzdWVzLg0NCUFDVElPTlM6DQ0JMS4JQUNDKE8pIHRvIGJy
+aW5nIGEgcGFwZXIgaW4gcmVzcGVjdCBvZiBHb2xkIENvbW1hbmQgaXNzdWVzIHRvIHRoZSBu
+ZXh0IHdlZWuScyBDT0cuICBPbmNlIGNvbXBsZXRlIGFuZCBhZ3JlZWQsIEFDQyhPKSB0byBi
+cmluZyB0b2dldGhlciBHb2xkIENvbW1hbmRlcnMgZm9yIGEgZm9ybWFsIG1lZXRpbmcuDQ0J
+Mi4JQS9BQ0MoT1MpIHRvIHVuZGVydGFrZSBhbiBhbmFseXNpcyBvZiB0aGUgcmVsYXRpdmUg
+ZmFjdHMgYW5kIGZpZ3VyZXMgaW4gcmVzcGVjdCBvZiBmaXJlYXJtcyBpbmNpZGVudHMsIHBh
+cnRpY3VsYXJseSB0aG9zZSByZWZlcnJlZCB0byBhdCBwYXJhZ3JhcGggMi4zIG9mIFN1cGVy
+aW50ZW5kZW50IER1Z2dsZWJ5knMgcmVwb3J0Lg0NMTguCVBheW1lbnQgb2YgRGlzcnVwdGlv
+biBBbGxvd2FuY2UgliBEZWRpY2F0ZWQgU291cmNlIFVuaXQgKERTVSkgQ29udHJvbGxlcnMg
+YW5kIEhhbmRsZXJzDQ0xOC4xCUFDTyhTKSBwcmVzZW50ZWQgYSBwYXBlciBwcmVwYXJlZCBq
+b2ludGx5IGJ5IHRoZSBIZWFkIG9mIFBlcnNvbm5lbCBhbmQgQ01CIGluIHJlc3BlY3Qgb2Yg
+dGhlIGFib3ZlIG1hdHRlci4gIFRoZSBwYXBlciBtYWRlIGEgbnVtYmVyIG9mIHJlY29tbWVu
+ZGF0aW9ucyB3aGljaCB3ZXJlIGNvbnNpZGVyZWQgYW5kIGRpc2N1c3NlZC4gIEhvd2V2ZXIs
+IGl0IHdhcyBhZ3JlZWQgdGhhdCBhdCB0aGlzIHN0YWdlIHRoZSByZWNvbW1lbmRhdGlvbnMg
+d291bGQgbm90IGJlIGltcGxlbWVudGVkIHVudGlsIGZ1cnRoZXIgaW5mb3JtYXRpb24gaW4g
+cmVzcGVjdCBvZiBTcGVjaWFsIFByaW9yaXR5IFBheW1lbnRzIHdhcyBvYnRhaW5lZC4gIEl0
+IHdhcyBjb25maXJtZWQgdGhhdCB1bnRpbCB0aGF0IGRlY2lzaW9uIHdhcyByZWFjaGVkIG9m
+ZmljZXJzIHdvdWxkIGNvbnRpbnVlIHRvIGJlIHJlY29tcGVuc2VkIHVuZGVyIHRoZSBleGlz
+dGluZyBhcnJhbmdlbWVudHMuDQ0JQUNUSU9OOiAgQUNPKFMpIHRvIGNsYXJpZnkgdGhlIHBv
+c2l0aW9uIGluIHJlc3BlY3Qgb2YgU1BQIGFuZCwgaWYgbmVjZXNzYXJ5LCBicmluZyBhIGZ1
+cnRoZXIgcGFwZXIgdG8gYSBmdXR1cmUgQ09HLg0NMTkuCVByb3Bvc2FsIHRvIGFkanVzdCBM
+UFQgYXJyYW5nZW1lbnRzIG9uIEMgRGl2aXNpb24NDTE5LjEJQUNDKE8pIHByZXNlbnRlZCBh
+IHBhcGVyIHByb2R1Y2VkIGJ5IEEvQ2hpZWYgU3VwZXJpbnRlbmRlbnQgRnVybG9uZywgcHJv
+cG9zaW5nIGFkanVzdG1lbnRzIHRvIHRoZSBkZXBsb3ltZW50IG9mIEluc3BlY3RvcnMgb24g
+YSBDIERpdiBMUFQuIFRoZSBDaGllZiBDb25zdGFibGUgc2FpZCB0aGF0IHdoaWxzdCB0aGUg
+b3BlcmF0aW9uYWwgZGVwbG95bWVudCBvZiBJbnNwZWN0b3JzIHdhcyBhIG1hdHRlciBmb3Ig
+dGhlIGRpdmlzaW9uLCBubyBjaGFuZ2VzIGluIHJlc3BlY3Qgb2YgTFBUIENvbW1hbmRlcnMg
+c2hvdWxkIGJlIG1hZGUgdW50aWwgYWZ0ZXIgaGlzIHByZXNlbnRhdGlvbiB0byB0aGUgUG9s
+aWNlIEF1dGhvcml0eSBvbiAyMCBTZXB0ZW1iZXIuDQ0yMC4JQ2hpZWYgT2ZmaWNlcnOSIFBB
+cw0NMjAuMQlDaGllZiBTdXBlcmludGVuZGVudCBDaGVlc2VtYW4gb3V0bGluZWQgcHJvcG9z
+YWxzIG1hZGUgYnkgQ2hpZWYgT2ZmaWNlcnOSIFBBcywgdG8gdW5kZXJ0YWtlIGZsZXhpYmxl
+IGhvdXJzIHdvcmtpbmcgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBjdXJyZW50IHByYWN0aWNl
+IGRpcmVjdGlvbiBhbmQgdGhlIHZpZXdzIG9mIENoaWVmIE9mZmljZXJzIHdlcmUgc291Z2h0
+LiAgT24gdGhlIHVuZGVyc3RhbmRpbmcgdGhhdCBjb3JlIHRpbWVzIHdvdWxkIGJlIGFncmVl
+ZCBhbmQgY292ZXJlZCBhbmQgdGhhdCB0aGUgb2ZmaWNlIHdvdWxkIGJlIGNvdmVyZWQgdGhy
+b3VnaG91dCB0aGUgd29ya2luZyBkYXksIHRoZXkgc3VwcG9ydGVkIHRoZSBwcm9wb3NhbCBh
+bmQgYXNrZWQgdGhhdCBpdCBiZSBpbXBsZW1lbnRlZCBmb3J0aHdpdGguIA0NCUFDVElPTjog
+IENoaWVmIFN1cGVyaW50ZW5kZW50IENoZWVzZW1hbiB0byBtYWtlIHRoZSBuZWNlc3Nhcnkg
+YXJyYW5nZW1lbnRzIHdpdGggQ2hpZWYgT2ZmaWNlcnOSIFBBcy4gDQ0yMS4JSW1wbGljYXRp
+b25zIG9mIHRoZSBOYXRpb25hbCBQb2xpY2luZyBDZW50cmUgb2YgRXhjZWxsZW5jZSBHdWlk
+YW5jZSBvbiB0aGUgU2FmZXIgRGV0ZW50aW9uIGFuZCBIYW5kbGluZyBvZiBQZXJzb25zIGlu
+IFBvbGljZSBDdXN0b2R5DQ0yMS4xCUEvQUNDKE9TKSBwcmVzZW50ZWQgYSBwYXBlciBwcmVw
+YXJlZCBieSBTdXBlcmludGVuZGVudCBCYWdzaGF3LCBvdXRsaW5pbmcgd29yayB0aGF0IHdh
+cyByZXF1aXJlZCB0byBiZSBkb25lIG92ZXIgdGhlIG5leHQgMTggbW9udGhzIGluIHJlc3Bl
+Y3Qgb2YgY3VzdG9keSwgcHJvdmlzaW9uIHJlbGF0ZWQgcHJvamVjdHMsIGF1ZGl0cyBhbmQg
+cmV2aWV3cy4gIENoaWVmIE9mZmljZXJzIGFncmVlZCB0aGVyZSB3YXMgYSBjb25zaWRlcmFi
+bGUgYW1vdW50IG9mIHdvcmsgcmVxdWlyZWQgYW5kIHNvbWUgZmFpcmx5IHRpZ2h0IHRpbWVz
+Y2FsZXMuICBUaGUgcGFwZXIgcHJvcG9zZWQgdGhhdCBJbnNwZWN0b3IgSG9kZ3Nvbiwgd2hv
+IGhhZCBub3cgYmVlbiByZXBsYWNlZCB3aXRoaW4gdGhlIENTQ0Mgc2hvdWxkIHRyYW5zZmVy
+IHRvIEFKVSBhbmQgdW5kZXJ0YWtlIHRoaXMgd29yay4gIEhvd2V2ZXIsIENoaWVmIE9mZmlj
+ZXJzIGRpZCBub3Qgc3VwcG9ydCB0aGUgcmVjb21tZW5kYXRpb24gaW4gdGhlIHBhcGVyIHRo
+YXQgdGhlIGNvc3RzIHNob3VsZCBiZSBwaWNrZWQgdXAgZnJvbSB0aGUgY2VudHJlIGFuZCBh
+ZHZpc2VkIHRoYXQgdGhlc2UgY29zdHMgYmUgbWV0IGZyb20gd2l0aGluIE9wcyBCcmFuY2gg
+ZGV2b2x2ZWQgYnVkZ2V0Lg0NMjIuCU5hdGlvbmFsIFBvbGljZSBNZW1vcmlhbCBEYXkNDTIy
+LjEJQUNDKE8pIHJldHVybmVkIHRoaXMgbWF0dGVyIHRvIENPRyBhbmQgQ2hpZWYgT2ZmaWNl
+cnMgYWdyZWVkIHRoYXQgaXQgd2FzIGltcG9ydGFudCB0aGF0IG9uZSBvZiB0aGVtIGF0dGVu
+ZGVkIG9uIDIgT2N0b2JlciBpbiBDYXJkaWZmIGZvciB0aGlzIGV2ZW50LiAgQUNDKE8pIHdh
+cyB0YXNrZWQgd2l0aCBjb250YWN0aW5nIE1yIEdyaWZmaW4gdG8gc2VlIGlmIGhlIHdvdWxk
+IGJlIGF2YWlsYWJsZSB0byByZXByZXNlbnQgdGhlIEZvcmNlLCBnaXZlbiB0aGF0IGhlIHdv
+dWxkIGJlIERlcHV0eSBDaGllZiBDb25zdGFibGUgYXQgdGhhdCB0aW1lLg0NMjMuCU1vdmVt
+ZW50IG9mIE9mZmljZXJzIGJldHdlZW4gRGl2aXNpb25zDQ0yMy4xCUFDQyhPKSBvdXRsaW5l
+ZCBzb21lIGRpZmZpY3VsdGllcyB0aGF0IHdlcmUgYmVpbmcgZW5jb3VudGVyZWQgaW4gcmVz
+cGVjdCBvZiB0aGUgbGFjayBvZiBndWlkYW5jZSB3aGVuIHJlcXVlc3RzIHdlcmUgbWFkZSBm
+b3Igb2ZmaWNlcnMgdG8gbW92ZSBiZXR3ZWVuIGRpdmlzaW9ucy4gIEFmdGVyIGRpc2N1c3Np
+b24sIGl0IHdhcyBhZ3JlZWQgYXMgZm9sbG93czoNDQlBQ1RJT046ICBBQ0MoTykgdG8gYXJy
+YW5nZSBmb3IgZGl2aXNpb25hbCBQICYgUyBvZmZpY2VycyB0byBjb21lIHRvZ2V0aGVyIHdp
+dGggYSBtZW1iZXIgb2YgUGVyc29ubmVsIEJyYW5jaCBhbmQgZHJhdyB1cCBzb21lIGd1aWRh
+bmNlIGZvciB1c2UgaW4gZnV0dXJlIHJlcXVlc3RzIGZyb20gb2ZmaWNlcnMuDQ0yNC4JR3Vu
+IGFuZCBLbmlmZSBDcmltZQ0NMjQuMQlUaGUgQ2hpZWYgQ29uc3RhYmxlIG91dGxpbmVkIGEg
+bGV0dGVyIGhlIGhhZCByZWNlaXZlZCBmcm9tIEdvdmVybm1lbnQgT2ZmaWNlLCBvZmZlcmlu
+ZyB0aGUgRm9yY2UgozMwLDAwMCB0byBiZSBzcGVudCBieSAzMSBNYXJjaCAyMDA2LCBvbiBy
+ZWR1Y2luZyBndW4gYW5kIGtuaWZlIGNyaW1lLiAgSGUgd2FzIGtlZW4gdG8gZW5zdXJlIHRo
+YXQgdGhlIEZvcmNlIHV0aWxpc2VkIHRoZWlyIGFsbG9jYXRpb24uIEl0IHdhcyBhZ3JlZWQg
+dGhhdCBBL0FDQyhPUykgd291bGQgbGVhZCBvbiB0aGlzIG1hdHRlciBhbmQgYnJpbmcgcHJv
+cG9zYWxzIGJhY2sgdG8gQ09HLCBvbmNlIGFwcHJvcHJpYXRlIGNvbnN1bHRhdGlvbiBoYWQg
+dGFrZW4gcGxhY2UuDQ0JQUNUSU9OOiAgQS9BQ0MoT1MpIHRvIGJyaW5nIHByb3Bvc2FscyBi
+YWNrIHRvIENPRy4NDTI1LglQb2xpY2UgQXV0aG9yaXR5IE1lZXRpbmcgliAyMCBTZXB0ZW1i
+ZXINDTI1LjEJVGhlIENoaWVmIENvbnN0YWJsZSB1cGRhdGVkIGNvbGxlYWd1ZXMgb24gdHdv
+IGl0ZW1zIGhlIGhhZCBiZWVuIGFza2VkIHRvIHByZXNlbnQgdG8gdGhlIFBvbGljZSBBdXRo
+b3JpdHkgbWVldGluZyB0byBiZSBoZWxkIG9uIDIwIFNlcHRlbWJlciwgbmFtZWx5IGluY2lk
+ZW50IGhhbmRsaW5nIGlzc3VlcyBhbmQgdGhlIGZ1dHVyZSBvZiBsb2NhbCBwb2xpY2luZy4g
+IEl0IHdhcyBhZ3JlZWQgdGhhdCB0aGUgcHJlc2VudGF0aW9uIG9uIGxvY2FsIHBvbGljaW5n
+IHdvdWxkIGJlIGJ5IHdheSBvZiBhIFBvd2VycG9pbnQgcHJlc2VudGF0aW9uIGFuZCBhIHNt
+YWxsIGdyb3VwIG9mIHRob3NlIHN0YWZmIHdpdGggY3VycmVudCBrbm93bGVkZ2Ugb2YgbmVp
+Z2hib3VyaG9vZCBwb2xpY2luZyBpc3N1ZXMsIHdvdWxkIGNvbWUgdG9nZXRoZXIgd2l0aCB0
+aGUgQ2hpZWYgQ29uc3RhYmxlIHRvIGFzc2lzdCBpbiBwcmVwYXJhdGlvbiBvZiB0aGlzIHBy
+ZXNlbnRhdGlvbi4NDUF0dGFjaGVkOiAJCUNvbXBsZXRlZC9PdXRzdGFuZGluZyBBY3Rpb25z
+IA0NTmV4dCBNZWV0aW5nIAlUaHVyc2RheSAyNSBBdWd1c3QgMjAwNSANAw0NBA0NAw0NBA0N
+DQ1Ob3QgUHJvdGVjdGl2ZWx5IE1hcmtlZA0NDQ0NQ09HIE1pbnV0ZXMgMTcuOC4wNSAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90IFByb3RlY3RpdmVseSBNYXJrZWQN
+RmlsZSBjbGFzc2lmaWNhdGlvbjogTk9UIFBST1RFQ1RJVkVMWSBNQVJLRUQgLSBOTyBERVND
+UklQVE9SDQ0NDQ0NDQ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAYAAAsIAAAmCAAAJwgAACwIAAAzCAAAPQgAAD4IAAA/CAAARwgAAEsIAABM
+CAAAawgAAGwIAABtCAAAeggAAHsIAAB+CAAAiwgAAJoIAACiCAAApAgAAKwIAAC1CAAAtggA
+ALoIAADYCAAA2ggAANwIAAD5CAAA+ggAAPsIAAD8CAAACQkAAAoJAAAMCQAADQkAABYJAAAc
+CQAAKgkAADEJAAAzCQAA/Pj88+7p8+Tp39rV2se4s66ppaGlnaWdpZmllJClia6EoX2EeXWh
+nW4AAAAAAAAAAAAAAAwVaBwTowAWaPsqYwAABhZoiFqHAAAGFmicWwMAAAwVaGg4mAAWaPsq
+YwAACRZo+ypjADUIgQwVaMxyEgAWaMxyEgAABhZozHISAAAJFmjMchIANQiBBhZoKFdiAAAG
+FmhNBNoAAAYWaPoMewAABhZoHnkbAAAJFmgeeRsANQiBCRZo1TKBADUIgQkWaHNCfgA1CIEd
+FmgfbvYAPioBQioGaAgAbUgJBHBo/wAAAHNICQQaFmgfbvYAQioGaAgAbUgJBHBo/wAAAHNI
+CQQACRZo/0IWADUIgQkWaB9u9gA1CIEJFmiKCY0ANQiBCRZonFsDADUIgQkWaChXYgA1CIEJ
+FmjRFI4ANQiBCRZoTQTaADUIgQYWaP9CFgAABhZoH272ACkABgAAJwgAAEwIAABsCAAAbQgA
+AHoIAAB7CAAAiQgAAIoIAADaCAAA+ggAAPsIAAAICQAACQkAADIJAAAzCQAAVgkAAFcJAAD8
+AAAAAAAAAAAAAAAA9gAAAAAAAAAAAAAAAPYAAAAAAAAAAAAAAADwAAAAAAAAAAAAAAAA5wAA
+AAAAAAAAAAAAAN8AAAAAAAAAAAAAAADXAAAAAAAAAAAAAAAAzwAAAAAAAAAAAAAAAL8AAAAA
+AAAAAAAAAADfAAAAAAAAAAAAAAAA3wAAAAAAAAAAAAAAALoAAAAAAAAAAAAAAAC6AAAAAAAA
+AAAAAAAAugAAAAAAAAAAAAAAAN8AAAAAAAAAAAAAAACmAAAAAAAAAAAAAAAA3wAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAADJAMNxgUA
+AdACAA+EoAURhGD6XoSgBWCEYPphJANnZFE5/wAABAAAZ2T7KmMAAA8AAAMkAw+E0AIRhDD9
+XoTQAmCEMP1hJANnZB55GwAABwAAAyQDYSQDZ2QeeRsAAAcAAAMkA2EkA2dk1TKBAAAHAAAD
+JANhJANnZHNCfgAJDwADJANAJgBhJANnZBw7aAAGDwADJABAJgBhJAAGAAADJAFAJgBhJAED
+DwBAJgAAEQAGAACnOAAAYjkAAP39AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAAQECMwkAADQJAAA2CQAAVwkAAFgJAABr
+CQAAdQkAAHcJAAB/CQAAiQkAAJAJAACRCQAAkgkAAJMJAACUCQAAlQkAALMJAAC0CQAAtwkA
+ANgJAADsCQAA7gkAAO8JAADyCQAABQoAAAcKAAALCgAAWQoAAFoKAAB1CgAAdgoAAHcKAAB6
+CgAAogoAAKQKAACoCgAAKAwAACoMAAArDAAA6QwAAOoMAADsDAAA0A0AANENAAD69fDs6OTg
+5Ojg5NH6zMfCvrq2srayraitpKCcoJSPraitpKCLh6CDoH52AAAAAAAAAAAAAAAAAAAAAAAP
+FWjrAWwAFmjrAWwANQiBCRZo6wFsADUIgQYWaE5mUQAABhZoBmNIAAAGFmjKT8gAAAkWaE0E
+2gA1CIEPFWiZD4wAFmjrAWwANQiBBhZomQ+MAAAGFmjrAWwAAAYWaNUygQAACRZonFsDADUI
+gQkWaNUygQA1CIEGFmhnE1gAAAYWaL47oAAABhZobDb4AAAGFmj6DHsAAAkWaL47oAA1CIEJ
+FmhseTEANQiBCRZoNkgQADUIgR0WaPtFhwA+KgFCKgZoCABtSAkEcGj/AAAAc0gJBAYWaJxb
+AwAABhZoTQTaAAAGFmhzQn4AAAYWaFE5/wAACRZoc0J+ADUIgQkWaBwTowA1CIEJFmj6DHsA
+NQiBACtXCQAAkQkAAJIJAACyCQAAswkAAO4JAADvCQAABgoAAAcKAABaCgAAdgoAAHcKAACj
+CgAApAoAAOoMAADrDAAA0Q0AANINAADyDQAA8w0AAIoPAACLDwAA7wAAAAAAAAAAAAAAAO8A
+AAAAAAAAAAAAAADnAAAAAAAAAAAAAAAA3wAAAAAAAAAAAAAAAN8AAAAAAAAAAAAAAADfAAAA
+AAAAAAAAAAAAywAAAAAAAAAAAAAAAMsAAAAAAAAAAAAAAADLAAAAAAAAAAAAAAAAywAAAAAA
+AAAAAAAAAMsAAAAAAAAAAAAAAADLAAAAAAAAAAAAAAAAywAAAAAAAAAAAAAAAMsAAAAAAAAA
+AAAAAADLAAAAAAAAAAAAAAAAywAAAAAAAAAAAAAAAMsAAAAAAAAAAAAAAADLAAAAAAAAAAAA
+AAAAywAAAAAAAAAAAAAAAMsAAAAAAAAAAAAAAADLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAAAMkAw3GBQAB0AIAD4TQAhGE
+MP1ehNACYIQw/WEkA2dk+0WHAAAHAAADJANhJANnZPcETAAABwAAAyQDYSQDZ2RnE1gAAA8A
+AAMkAw+E0AIRhDD9XoTQAmCEMP1hJANnZPtFhwAAFdENAADSDQAA1Q0AAPENAADzDQAA9g0A
+AI8PAACXDwAAmA8AANoQAADbEAAA3BAAAN0QAADqEAAA7BAAAAYRAAAJEQAAPRIAAOwSAADt
+EgAA/RIAAAcTAAB1EwAAdhMAAHgTAAAuFAAALxQAADAUAABDFAAARhQAALEUAADdFAAA7RUA
+AO4VAADwFQAAohYAAKMWAACmFgAApxYAAL4WAADCFgAAwxYAADYaAAA3GgAAORoAAEkaAADR
+GgAAuhsAALsbAAC8GwAA0xsAANcbAADYGwAAvxwAAMAcAADCHAAAHR0AAPfy7fLp5eDY4OnU
+0O3L7dDlx8PHw8fQx762su3Qx8PH0Me+tu2t7dDHqdCppJyknNDt0KmY0JiTAAAAAAAAAAAA
+AAkWaBVLGAA1CIEGFmgVSxgAAA8VaM13jwAWaM13jwA1CIEJFmjNd48ANQiBBhZozXePAAAJ
+FmgcE6MANQiBBhZo+gx7AAAPFWhbIPEAFmhbIPEANQiBCRZoWyDxADUIgQYWaHRItgAABhZo
+WyDxAAAJFmjJRGEANQiBBhZonFsDAAAGFmhOZlEAAA8VaOsBbAAWaOsBbAA1CIEJFmjrAWwA
+NQiBBhZo6wFsAAAGFmgcc9EAAAkWaJxbAwA1CIEJFmgcc9EANQiBDxVoTmZRABZoMWy5ADUI
+gQA4iw8AAI0PAACODwAAlw8AAJgPAADcEAAA3RAAAAURAAAGEQAAdhMAAHcTAAAvFAAAMBQA
+AEIUAABDFAAA7hUAAO8VAACjFgAApBYAAL0WAAC+FgAANxoAAOsAAAAAAAAAAAAAAADrAAAA
+AAAAAAAAAAAA6wAAAAAAAAAAAAAAAOsAAAAAAAAAAAAAAADrAAAAAAAAAAAAAAAA1wAAAAAA
+AAAAAAAAANcAAAAAAAAAAAAAAADXAAAAAAAAAAAAAAAA1wAAAAAAAAAAAAAAANcAAAAAAAAA
+AAAAAADXAAAAAAAAAAAAAAAAywAAAAAAAAAAAAAAALcAAAAAAAAAAAAAAAC3AAAAAAAAAAAA
+AAAAtwAAAAAAAAAAAAAAALcAAAAAAAAAAAAAAAC3AAAAAAAAAAAAAAAAtwAAAAAAAAAAAAAA
+ALcAAAAAAAAAAAAAAAC3AAAAAAAAAAAAAAAAtwAAAAAAAAAAAAAAAAAAAAAAABMAAAMkAw3G
+BQAB0AIAD4TQAhGEMP1ehNACYIQw/WEkA2dkHBOjAAALAAADJAMNxgUAAdACAGEkA2dkTQTa
+AAATAAADJAMNxgUAAdACAA+E0AIRhDD9XoTQAmCEMP1hJANnZPtFhwAAEwAAAyQDDcYFAAHQ
+AgAPhNACEYQw/V6E0AJghDD9YSQDZ2TrAWwAABU3GgAAOBoAAEQaAABFGgAA0hoAANMaAAC7
+GwAAvBsAANIbAADTGwAAwBwAAMEcAAAeHQAAHx0AAEcdAABIHQAAXB4AAF0eAAB2HgAAdx4A
+AKAeAAChHgAA3h4AAN8eAADrAAAAAAAAAAAAAAAA6wAAAAAAAAAAAAAAAOsAAAAAAAAAAAAA
+AADXAAAAAAAAAAAAAAAA1wAAAAAAAAAAAAAAANcAAAAAAAAAAAAAAADrAAAAAAAAAAAAAAAA
+6wAAAAAAAAAAAAAAAOsAAAAAAAAAAAAAAADrAAAAAAAAAAAAAAAA6wAAAAAAAAAAAAAAAOsA
+AAAAAAAAAAAAAADrAAAAAAAAAAAAAAAA6wAAAAAAAAAAAAAAAMcAAAAAAAAAAAAAAADHAAAA
+AAAAAAAAAAAAxwAAAAAAAAAAAAAAAMcAAAAAAAAAAAAAAADHAAAAAAAAAAAAAAAAxwAAAAAA
+AAAAAAAAAMcAAAAAAAAAAAAAAADHAAAAAAAAAAAAAAAAxwAAAAAAAAAAAAAAAAAAAAAPAAAD
+JAMPhNACEYQw/V6E0AJghDD9YSQDZ2QcO2gAABMAAAMkAw3GBQAB0AIAD4SgBRGEYPpehKAF
+YIRg+mEkA2dkzXePAAATAAADJAMNxgUAAdACAA+E0AIRhDD9XoTQAmCEMP1hJANnZBwTowAA
+Fx0dAAAeHQAAHx0AACMdAABIHQAASh0AAEwdAADBHQAAEB4AABQeAABbHgAAXB4AAF0eAABf
+HgAAdx4AAHkeAAB6HgAAfB4AAJ8eAACgHgAAoR4AAKIeAACjHgAA3h4AAN8eAADgHgAA4R4A
+AOMeAADkHgAAAx8AAAQfAAAFHwAAFB8AABUfAAAWHwAAFx8AABgfAAAoHwAALB8AAPEhAADy
+IQAAGSMAABojAAAfIwAAMSMAAPfz7unz5eHd2d3V0e7p883JxcnAu+62sdHz5a3hppeIg7vu
+fu7z4fPhee50AAAAAAAAAAAJFmhZcSoANQiBCRZoFUsYADUIgQkWaBJOTAA1CIEJFmgfbvYA
+NQiBHRZoH272AD4qAUIqBmgIAG1ICQRwaP8AAABzSAkEHRZozzL0AD4qAUIqBmgIAG1ICQRw
+aP8AAABzSAkEDBVoFUsYABZocmqrAAAGFmgsbwAAAAkWaLkXxgA1CIEJFmgsFp0ANQiBCRZo
+TQTaADUIgQkWaFE5/wA1CIEGFmgGYLgAAAYWaLkXxgAABhZoHBOjAAAGFmhNBNoAAAYWaPoM
+ewAABhZo+DXXAAAGFmh0SLYAAAYWaBVLGAAABhZo90xhAAAJFmjDLcIANQiBCRZonFsDADUI
+gQYWaJxbAwAADxVoFUsYABZoFUsYADUIgQAs3x4AAAQfAAAFHwAAFB8AABUfAAAnHwAAKB8A
+APIhAADzIQAACyMAABojAAAbIwAAQiMAAEMjAADHIwAAySMAAMojAADLIwAA7CMAAO0jAAA9
+JgAAPiYAAEgmAADvAAAAAAAAAAAAAAAA5gAAAAAAAAAAAAAAAOYAAAAAAAAAAAAAAADeAAAA
+AAAAAAAAAAAAzgAAAAAAAAAAAAAAAM4AAAAAAAAAAAAAAADOAAAAAAAAAAAAAAAAzgAAAAAA
+AAAAAAAAAM4AAAAAAAAAAAAAAADOAAAAAAAAAAAAAAAAzgAAAAAAAAAAAAAAAM4AAAAAAAAA
+AAAAAADOAAAAAAAAAAAAAAAAzgAAAAAAAAAAAAAAAL4AAAAAAAAAAAAAAAC+AAAAAAAAAAAA
+AAAAvgAAAAAAAAAAAAAAAL4AAAAAAAAAAAAAAAC+AAAAAAAAAAAAAAAAvgAAAAAAAAAAAAAA
+AL4AAAAAAAAAAAAAAAC+AAAAAAAAAAAAAAAAAAAQGgAKJgALRgAAD4TQAhGEMP1ehNACYIQw
+/WdkK3MmAAAPAAADJAMPhNACEYQw/V6E0AJghDD9YSQDZ2QcO2gAAAcAAAMkA2EkA2dkHDto
+AAkPAAMkA0AmAGEkA2dkHDtoAAAPAAADJAMPhNACEYQw/V6E0AJghDD9YSQDZ2ScWwMAABYx
+IwAAQSMAAEIjAABDIwAARCMAAEUjAABHIwAASCMAAMUjAADGIwAAyCMAAMkjAADLIwAAzCMA
+AM0jAADPIwAA6yMAAOwjAADtIwAA8SMAAPIjAAA8JgAAPSYAAD8mAABKJgAATCYAAJ4mAAD5
+JgAAuycAABcoAAAbKAAAgSkAAJgpAAC2KQAAtykAAAQqAAB3KgAAeCoAAHkqAACJKgAArioA
+ALAqAAC0KgAAAysAAEArAADTKwAA1CsAAO8rAAAcLAAAKiwAACssAABELAAASCwAABwtAABm
+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
+/wAAAHNICQQAHxVoyURhABZoBmNIAENKEgBhShIAaAgAbUgJBHNICQQGFmgGY0gAABEWaAZj
+SABoCABtSAkEc0gJBAYWaNtkbwAABhZoR1fNAAAPA2oAAAAAFmhHV80AVQgBABfROAAAHzkA
+AFw5AABdOQAAXjkAAF85AABgOQAAYTkAAGI5AABjOQAA9gAAAAAAAAAAAAAAAPYAAAAAAAAA
+AAAAAAD0AAAAAAAAAAAAAAAA8gAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAD0AAAAAAAAAAAA
+AAAA8AAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAADnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAADJANAJgBhJANnZBw7aAAAAQAAAAEVAAABFgAJ
+DwADJABAJgBhJABnZMlEYQAACSgAH7CCLiCwxkEhsNACIrDQAiOQxQIkkG8EJbAAABewwgIY
+sMICDJDQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIYCGwASAAEAnAAPAAQAAAAA
+AAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+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
+ALMBAADuAQAA7wEAAAYCAAAHAgAAWgIAAHYCAAB3AgAAowIAAKQCAADqBAAA6wQAANEFAADS
+BQAA8gUAAPMFAACKBwAAiwcAAI0HAACOBwAAlwcAAJgHAADcCAAA3QgAAAUJAAAGCQAAdgsA
+AHcLAAAvDAAAMAwAAEIMAABDDAAA7g0AAO8NAACjDgAApA4AAL0OAAC+DgAANxIAADgSAABE
+EgAARRIAANISAADTEgAAuxMAALwTAADSEwAA0xMAAMAUAADBFAAAHhUAAB8VAABHFQAASBUA
+AFwWAABdFgAAdhYAAHcWAACgFgAAoRYAAN4WAADfFgAABBcAAAUXAAAUFwAAFRcAACcXAAAo
+FwAA8hkAAPMZAAALGwAAGhsAABsbAABCGwAAQxsAAMcbAADJGwAAyhsAAMsbAADsGwAA7RsA
+AD0eAAA+HgAASB4AAEkeAAD5HgAA+h4AALofAAC7HwAAFiAAABcgAAACIgAAAyIAAHgiAAB5
+IgAAryIAALAiAAAqJAAAKyQAAEMkAABEJAAA5yUAAOglAABPJgAAUCYAANkmAADaJgAAYikA
+AGMpAACEKQAAhSkAAMEqAADCKgAA7SoAAO4qAADBKwAAwisAAHMsAAB0LAAAjCwAAI0sAAAC
+LgAAAy4AADcuAAA4LgAAZC4AAGUuAABTMAAAVDAAAH8wAACAMAAApzAAAKkwAACqMAAArDAA
+AK0wAACvMAAAsDAAALIwAACzMAAAzTAAAM4wAADPMAAAXDEAAF0xAABkMQAACAAAAA8wAAAA
+AAAAAIAAAACAAAAAAAAAAAAAAAgAAAAAMAAAAAAAAACAAAAAgAAAAAAAAAAAAAAIAAAAADAA
+AAAAAAAAgAAAAIAAAAAAAAAAAAAACAAAAA8wAAAAAAAAAIAAAACAAAAAAAAAAAAAAAgAAAAP
+MAAAAAAAAACAAAAAgAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAA
+AAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJhAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACY
+AAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAA
+AJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAA
+AAAAmEAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAA
+AAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAA
+AAAAAAAAAJhAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAgACYAAAAADAAAAAAAAAAgG0AAAAA
+AAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAA
+AAAAAAAAAAAAAACYQAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAIAAmAAAAAAwAAAAAAAAAIBt
+AAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAA
+gG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAA
+AACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAA
+AAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAA
+AAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIAAAACAAAAAAAAAAAAAAJgAAAAA
+MAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAA
+AAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACY
+AAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAA
+AJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAA
+AAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAAAAAgAAAAAAA
+AAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAA
+AAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAA
+AAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAA
+AAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBt
+AAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAA
+gG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAA
+AACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAA
+AAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAA
+AAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAA
+MAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAA
+AAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACY
+AAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAA
+AJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAA
+AAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAA
+AAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAA
+AAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAA
+AAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAA
+AAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBt
+AAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAA
+gG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIBtAAAAAAAAAAAAAAAAAJgAAAAAMAAAAAAA
+AACAbQAAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgG0AAAAAAAAAAAAAAAAAmAAAAAAwAAAA
+AAAAAIBtAAAAAAAAAAAAAACAAJgAAAAAMAAAAAAAAACAbQAAAAAAAAAAAAAAAAAIAAAADzAA
+AAAAAAAAgAAAAIAAAAAAAAAAAAAACAAAAA8wAAAAAAAAAIAAAACAAAAAAAAAAAAAAJgAAAAA
+MAAAAAAAAACABhcAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgAYXAAAAAAAAAAAAAAAAmAAA
+AAAwAAAAAAAAAIAGFwAAAAAAAAAAAACAAJgAAAAAMAAAAAAAAACABhcAAAAAAAAAAAAAAACY
+AAAAADAAAAAAAAAAgAYXAAAAAAAAAAAAAAAAmAAAAAAwAAAAAAAAAIAGFwAAAAAAAAAAAAAA
+AJgAAAAAMAAAAAAAAACABhcAAAAAAAAAAAAAAACYAAAAADAAAAAAAAAAgAYXAAAAAAAAAAAA
+AAAAmAAAAAAwAAAAAAAAAIAGFwAAAAAAAAAAAAAAAJgAAAAAMAAAAAAAAACABhcAAAAAAAAA
+AAAAAACYAAAAADAAAAAAAAAAgAYXAAAAAAAAAAAAAAAAmAAAABowAAAAAAAAAIAGFwAAAAAA
+AAAAAACAAJgAAAAaMAAAAAAAAACABhcAAAAAAAAAAAAAgACYAAAAGjAAAAAAAAAAgAYXAAAA
+AAAAAAAAAAAAmAAAABowAAAAAAAAAIAGFwAAAAAAAAAAAAAAAJgAAAAaMAAAAAAAAACABhcA
+AAAAAAAAAAAAgACYAAAAGjAAAAAAAAAAgAYXAAAAAAAAAAAAAAAAmAAAABowAAAAAAAAAIAG
+FwAAAAAAAAAAAAAAAJgAAAAaMAAAAAAAAACABhcAAAAAAAAAAAAAAACYAAAAGjAAAAAAAAAA
+gAYXAAAAAAAAAAAAAAAAmAAAABowAAAAAAAAAIAGFwAAAAAAAAAAAAAAAJgAAAAaMAAAAAAA
+AACAAAAAgAAAAAAAAAAAAACYAAAAGjAAAAAAAAAAgAYXAAAAAAAAAAAAAAAAmAAAABowAAAA
+AAAAAIAGFwAAAAAAAAAAAAAAAJgAAAAaMAAAAAAAAACABhcAAAAAAAAAAAAAAACYAAAAGjAA
+AAAAAAAAgAYXAAAAAAAAAAAAAIAAmAAAABowAAAAAAAAAIAGFwAAAAAAAAAAAAAAAJgAAAAa
+MAAAAAAAAACABhcAAAAAAAAAAAAAAACYAAAAGjAAAAAAAAAAgAYXAAAAAAAAAAAAAAAAmAAA
+ABowAAAAAAAAAIAGFwAAAAAAAAAAAAAAAJgAAAAaMAAAAAAAAACABhcAAAAAAAAAAAAAAACY
+AAAAGjAAAAAAAAAAgAYXAAAAAAAAAAAAAIAAmAAAABowAAAAAAAAAIAAAACAAAAAAAAAAAAA
+AJgAAAAaMAAAAAAAAACAQRYAAAAAAAAAAAAAAACYAAAAGjAAAAAAAAAAgEEWAAAAAAAAAAAA
+AAAAmAAAABowAAAAAAAAAIBBFgAAAAAAAAAAAACAAJgAAAAaMAAAAAAAAACAAAAAgAAAAAAA
+AAAAAACYAAAAGjAAAAAAAAAAgEEWAAAAAAAAAAAAAAAAmAAAABowAAAAAAAAAIAAAACAAAAA
+AAAAAAAAAJgAAAAaMAAAAAAAAACAAAAAgAAAAAAAAAAAAACYAAAAGjAAAAAAAAAAgEEWAAAA
+AAAAAAAAAAAAmAAAABowAAAAAAAAAIBBFgAAAAAAAAAAAAAAAJgAAAAaMAAAAAAAAACAAAAA
+gAAAAAAAAAAAAACYAAAAGjAAAAAAAAAAgEEWAAAAAAAAAAAAAAAAmAAAABowAAAAAAAAAIBB
+FgAAAAAAAAAAAAAAAJgAAAAaMAAAAAAAAACAQRYAAAAAAAAAAAAAAACYAAAAGjAAAAAAAAAA
+gEEWAAAAAAAAAAAAAAAAmAAAABowAAAAAAAAAIBBFgAAAAAAAAAAAAAAAJgAAAAaMAAAAAAA
+AACAQRYAAAAAAAAAAAAAAACYAAAAGjAAAAAAAAAAgEEWAAAAAAAAAAAAAAAAmAAAABowAAAA
+AAAAAIBBFgAAAAAAAAAAAAAAAJgAAAAaMAAAAAAAAACAQRYAAAAAAAAAAAAAAACYAAAAGjAA
+AAAAAAAAgEEWAAAAAAAAAAAAAAAAmAAAABowAAAAAAAAAIBBFgAAAAAAAAAAAAAAAJgAAAAa
+MAAAAAAAAACAQRYAAAAAAAAAAAAAAACYAAAAGjAAAAAAAAAAgEEWAAAAAAAAAAAAAAAAmAAA
+ABowAAAAAAAAAIAAAACAAAAAAAAAAAAAAJgAAAAaMAAAAAAAAACAQRYAAAAAAAAAAAAAAACY
+AAAAGjAAAAAAAAAAgEEWAAAAAAAAAAAAAAAAmAAAABowAAAAAAAAAIBBFgAAAAAAAAAAAAAA
+AJgAAAAaMAAAAAAAAACAQRYAAAAAAAAAAAAAAACYAAAAGjAAAAAAAAAAgEEWAAAAAAAAAAAA
+AAAAmAAAABowAAAAAAAAAIAAAACAAAAAAAAAAAAAAJgAAAAaMAAAAAAAAACAQRYAAAAAAAAA
+AAAAgACYAAAAEzAAAAAAAAAAgAAAAIAAAAAAAAAAAAAACAAAAAAwAAAAAAAAAIAAAACAAAAA
+AAAAAAAAAAgAAAAAMAAAAAAAAACAAAAAgAAAAAAAAAAAAACYQAAAADAAAAAAAAAAgAAAAIAA
+AAAAAAAAAAAHaIsAMAAwAAAAAAAAAQAAAAAAAAAAAAAAAACeB5hAAAAAMAAAAAAAAACAAAAA
+gAAAAAAAAAAAAAdoiwAwADAAAAAAAAABAAAAAAAAAAAAAAAAAJ4HmEAAAAAwAAAAAAAAAIAA
+AACAAAAAAAAAAAAAB2iLADAAMAAAAAAAAAEAAAAAAAAAAAAAAAAAngeYQAAAADAAAAAAAAAA
+gAAAAIAAAAAAAAAAAAAHaIsAMAAwAAAAAAAAAQAAAAAAAAAAAAAAAACeBwhAAAAPMAAAAAAA
+AACAAAAAgAAAAAAAAAAAAAcIQAAADzAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAmEAAAAAwAAAA
+AAAAAIAkAAAAAAAAAAAAAAAAAAhAAAAPMAAAAAAAAACAAAAAgAAAAAAAAAAAAAeYQAAAFjAA
+AAAAAAAAgCYAAAAAAAAAAAAAAIAAaIsAMAAwAAAAAAAAAQAAAAAAAAAAAAAAgAWeBwAAAAAn
+AAAATAAAAGwAAACJAAAAVgEAAFcBAACyAQAAswEAABQXAAAVFwAAfzAAAIAwAABkMQAACgAA
+AA8wAAAAAAAAAIAAAACAAAAAAAAAAAAABwoAAAAAMAAAAAAAAACAAAAAgAAAAAAAAAAAAAcK
+AAAAADAAAAAAAAAAgAAAAIAAAAAAAAAAAAAHmkAAAAAwAAAAAAAAAICVAAAAAAAAAAAAAAAA
+B5pAAAAAMAAAAAAAAACAzgEAAAAAAAAAAAAAAAeYQAAAADAAAAAAAAAAgM4BAAAAAAAAAAAA
+AAAAmkAAAAAwAAAAAAAAAIDOAQAAAAAAAAAAAAAAB5hAAAAAMAAAAAAAAACAzgEAAAAAAAAA
+AAAAAAAKAAAADzAAAAAAAAAAgAAAAIAAAAAAAAAAAIAHmgAAAAAwAAAAAAAAAIDNDQAAAAAA
+AAAAAACAB5oAAAATMAAAAAAAAACAzQ0AAAAAAAAAAAAAAAcKAAAAADAAAAAAAAAAgAAAAIAA
+AAAAAAAAAAAHmgAAAAAQAAAAAAAAAIAAAACAAAAAAAAAAIAABwAAAAADAAAABgAAAAYAAAAJ
+AAAADAAAAAwAAAAOAAAAKAAAACoAAAC2AAAAuAAAALoAAAC9AAAAAAYAADMJAADRDQAAHR0A
+ADEjAABmLQAAqjgAAGM5AAAdAAAAIAAAACIAAAAlAAAAJwAAACkAAAAsAAAAAAYAAFcJAACL
+DwAANxoAAN8eAABIJgAATy4AADc2AADROAAAYzkAAB4AAAAhAAAAIwAAACQAAAAmAAAAKAAA
+ACoAAAArAAAALQAAAAAGAABiOQAAHwAAAP//CQAAAAYArYvXChAAAQBEPdAPBgCui9cKEQAB
+ABwTEhMGAK+L1woQAAEABGTRDwYAsIvXChEAAQB0b9APBgCxi9cKEAABAGwQyQ8GALKL1woQ
+AAEAlBHKDwYAs4vXChEAAQD0R8kPBgC0i9cKEQABAOQByg8GALWL1woQAAEAfKkUE+MIAADj
+CAAAAQoAAK8VAACvFQAAVxYAAFcWAAAHKgAAByoAAGQxAAAAAAAAAgABAAAAAgACAAAAAQAD
+AAAAAgAEAAAAAgAFAAAAAgAGAAAAAgAHAAAAAgAIAAAAAgDsCAAA7AgAAAkKAACzFQAAsxUA
+AFkWAABZFgAADioAAA4qAABkMQAAAAAAAAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAI
+AAAAAwAAAEIAAAADAAAAKoB1cm46c2NoZW1hcy1taWNyb3NvZnQtY29tOm9mZmljZTpzbWFy
+dHRhZ3MOgGNvdW50cnktcmVnaW9uAIA5AAAACQAAACqAdXJuOnNjaGVtYXMtbWljcm9zb2Z0
+LWNvbTpvZmZpY2U6c21hcnR0YWdzBYBwbGFjZQCAOAAAAAgAAAAqgHVybjpzY2hlbWFzLW1p
+Y3Jvc29mdC1jb206b2ZmaWNlOnNtYXJ0dGFncwSAQ2l0eQCADAAAAUQRphMAAAAACQAAAAAA
+CAAAAAAACQAAAAAACAAAAAAACQAAAAAACQAAAAAAAwAAAAAACAAAAAAACQAAAAAAAAAAACsE
+AAAuBAAASSYAAEwmAACmMAAApzAAAKcwAACpMAAAqTAAAKowAACqMAAArDAAAK0wAACvMAAA
+sDAAALIwAAC1MAAAzzAAANEwAAAeMQAAWzEAAF0xAABhMQAAZDEAAAcABAAHABwABwACAAQA
+BwAEAAIABAAHAAQABwAEAAcABAAHAAQABwAEAAcABAACAAAAAABKAwAARAQAAEcMAABLDAAA
+ZRUAAGkVAAAAHwAABB8AALUiAAC5IgAAkC0AAJQtAACmMAAApzAAAKcwAACpMAAAqTAAAKow
+AACqMAAArDAAAK0wAACvMAAAsDAAALIwAAC1MAAAzzAAANEwAAAeMQAAWzEAAF0xAABhMQAA
+ZDEAAAcABAAHADMABwAzAAcAMwAHADMABwAzAAcAAgAEAAcABAACAAQABwAEAAcABAAHAAQA
+BwAEAAcABAAHAAQAAgAAAAAAKAQAACsEAACPBwAAjwcAABAWAAAUFgAA+R4AAPkeAABKJgAA
+SyYAAE4mAABPJgAAGikAACIpAAAyKQAANCkAADotAAB6LQAAJzAAADEwAAA3MAAAQTAAAFQw
+AABUMAAApjAAAKcwAACnMAAAqTAAAKkwAACqMAAAqjAAAKwwAACtMAAArzAAALAwAACyMAAA
+tTAAAM8wAAAHMQAAHjEAAFsxAABdMQAAYTEAAGQxAAADAAQAAwAEAAMABAADAAQAAwAEAAMA
+BAADAAQAAwAEAAMABAADAAQAAwAEAAMABAADAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIA
+BAACAAQAAgAEAAIAAAAAACsEAAAuBAAApjAAAKcwAACnMAAAqTAAAKkwAACqMAAAqjAAAKww
+AACtMAAArzAAALAwAACyMAAAtTAAAM8wAADRMAAAHjEAAFsxAABdMQAAYTEAAGQxAAAHAAQA
+BwACAAQABwAEAAIABAAHAAQABwAEAAcABAAHAAQABwAEAAcABAACAAwAif///wBZOvIaAP8P
+/w//D/8P/w//D/8P/w8BAEAMIxlyV2Iy/w//D/8P/w//D/8P/w//D/8PEACPUDwZVqEYOf8P
+/w//D/8P/w//D/8P/w//DwAAQFVGJCJsYo3/D/8P/w//D/8P/w//D/8P/w8AANFR/ihWdBD8
+/w//D/8P/w//D/8P/w//D/8PEACqLcI/4BFaBv8P/w//D/8P/w//D/8P/w//DwAAHSJaRmTu
+xib/D/8P/w//D/8P/w//D/8P/w8AAFpW90xC0mIm/w//D/8P/w//D/8P/w//D/8PEAD8IPlu
+9EjwN/8P/w//D/8P/w//D/8P/w//DwAAWynldS7w8p3/D/8P/w//D/8P/w//D/8P/w8QAIkj
+DHkCgeQ3/w//D/8P/w//D/8P/w//D/8PAABXAQh8fkscCv8P/w//D/8P/w//D/8P/w//DxAA
+AQAAABcAAAAAAAAAAAAAAAAAAAAAAAAACxgAAA+EaAERhJj+FcYFAAFoAQZehGgBYISY/k9K
+AQBRSgEAbygAAQC38AEAAAAXEAAAAAAAAAAAAABoAQAAAAAAABUYAAAPhDgEEYSY/hXGBQAB
+OAQGXoQ4BGCEmP5PSgEAUUoBAG8oAIdoAAAAAIhIAAABALfwAQAAABeQAAAAAAAAAAAAAGgB
+AAAAAAAAGRgAAA+ECAcRhJj+FcYFAAEIBwZehAgHYISY/k9KBABRSgQAXkoEAG8oAIdoAAAA
+AIhIAAABAG8AAQAAABeQAAAAAAAAAAAAAGgBAAAAAAAAFRgAAA+E2AkRhJj+FcYFAAHYCQZe
+hNgJYISY/k9KBQBRSgUAbygAh2gAAAAAiEgAAAEAp/ABAAAAF5AAAAAAAAAAAAAAaAEAAAAA
+AAAVGAAAD4SoDBGEmP4VxgUAAagMBl6EqAxghJj+T0oBAFFKAQBvKACHaAAAAACISAAAAQC3
+8AEAAAAXkAAAAAAAAAAAAABoAQAAAAAAABkYAAAPhHgPEYSY/hXGBQABeA8GXoR4D2CEmP5P
+SgQAUUoEAF5KBABvKACHaAAAAACISAAAAQBvAAEAAAAXkAAAAAAAAAAAAABoAQAAAAAAABUY
+AAAPhEgSEYSY/hXGBQABSBIGXoRIEmCEmP5PSgUAUUoFAG8oAIdoAAAAAIhIAAABAKfwAQAA
+ABeQAAAAAAAAAAAAAGgBAAAAAAAAFRgAAA+EGBURhJj+FcYFAAEYFQZehBgVYISY/k9KAQBR
+SgEAbygAh2gAAAAAiEgAAAEAt/ABAAAAF5AAAAAAAAAAAAAAaAEAAAAAAAAZGAAAD4ToFxGE
+mP4VxgUAAegXBl6E6BdghJj+T0oEAFFKBABeSgQAbygAh2gAAAAAiEgAAAEAbwABAAAAF5AA
+AAAAAAAAAAAAaAEAAAAAAAAVGAAAD4S4GhGEmP4VxgUAAbgaBl6EuBpghJj+T0oFAFFKBQBv
+KACHaAAAAACISAAAAQCn8AYAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAMYAAAPhNACEYQw/RXG
+BQAB0AIGXoTQAmCEMP1vKAACAAAALgABAAAAAAQBAwAAAAAAAAAAAAAAAAAAAAADGAAAD4Ro
+ARGEmP4VxgUAAWgBBl6EaAFghJj+bygAAwAAAC4AAQABAAAAFgQBAwUAAAAAAAAAAAAAAAAA
+AAADGAAAD4TQAhGEMP0VxgUAAdACBl6E0AJghDD9bygABQAAAC4AAQAuAAIAAQAAAAAEAQMF
+BwAAAAAAAAAAAAAAAAAAAxgAAA+EOAQRhMj7FcYFAAE4BAZehDgEYITI+28oAAcAAAAuAAEA
+LgACAC4AAwABAAAAAAQBAwUHCQAAAAAAAAAAAAAAAAADGAAAD4Q4BBGEyPsVxgUAATgEBl6E
+OARghMj7bygACQAAAC4AAQAuAAIALgADAC4ABAABAAAAAAQBAwUHCQsAAAAAAAAAAAAAAAAD
+GAAAD4SgBRGEYPoVxgUAAaAFBl6EoAVghGD6bygACwAAAC4AAQAuAAIALgADAC4ABAAuAAUA
+AQAAAAAEAQMFBwkLDQAAAAAAAAAAAAAAAxgAAA+EoAURhGD6FcYFAAGgBQZehKAFYIRg+m8o
+AA0AAAAuAAEALgACAC4AAwAuAAQALgAFAC4ABgABAAAAAAQBAwUHCQsNDwAAAAAAAAAAAAAD
+GAAAD4QIBxGE+PgVxgUAAQgHBl6ECAdghPj4bygADwAAAC4AAQAuAAIALgADAC4ABAAuAAUA
+LgAGAC4ABwABAAAAAAQBAwUHCQsNDxEAAAAAAAAAAAADGAAAD4QIBxGE+PgVxgUAAQgHBl6E
+CAdghPj4bygAEQAAAC4AAQAuAAIALgADAC4ABAAuAAUALgAGAC4ABwAuAAgABwAAAAAAAQAA
+AAAAAAAAAAAAAAAAAAAAAxgAAA+EaAERhJj+FcYFAAFoAQZehGgBYISY/m8oAAEAAAABAAAA
+AAABAwAAAAAAAAAAAAAAAAAAAAADGAAAD4RoARGEmP4VxgUAAWgBBl6EaAFghJj+bygAAwAA
+AC4AAQABAAAAAAABAwUAAAAAAAAAAAAAAAAAAAADGAAAD4TQAhGEMP0VxgUAAdACBl6E0AJg
+hDD9bygABQAAAC4AAQAuAAIAAQAAAAAAAQMFBwAAAAAAAAAAAAAAAAAAAxgAAA+EOAQRhMj7
+FcYFAAE4BAZehDgEYITI+28oAAcAAAAuAAEALgACAC4AAwABAAAAAAABAwUHCQAAAAAAAAAA
+AAAAAAADGAAAD4Q4BBGEyPsVxgUAATgEBl6EOARghMj7bygACQAAAC4AAQAuAAIALgADAC4A
+BAABAAAAAAABAwUHCQsAAAAAAAAAAAAAAAADGAAAD4SgBRGEYPoVxgUAAaAFBl6EoAVghGD6
+bygACwAAAC4AAQAuAAIALgADAC4ABAAuAAUAAQAAAAAAAQMFBwkLDQAAAAAAAAAAAAAAAxgA
+AA+EoAURhGD6FcYFAAGgBQZehKAFYIRg+m8oAA0AAAAuAAEALgACAC4AAwAuAAQALgAFAC4A
+BgABAAAAAAABAwUHCQsNDwAAAAAAAAAAAAADGAAAD4QIBxGE+PgVxgUAAQgHBl6ECAdghPj4
+bygADwAAAC4AAQAuAAIALgADAC4ABAAuAAUALgAGAC4ABwABAAAAAAABAwUHCQsNDxEAAAAA
+AAAAAAADGAAAD4QIBxGE+PgVxgUAAQgHBl6ECAdghPj4bygAEQAAAC4AAQAuAAIALgADAC4A
+BAAuAAUALgAGAC4ABwAuAAgAAQAAABcQAAAAAAAAAAAAAGgBAAAAAAAAFRgAAA+EOAQRhJj+
+FcYFAAE4BAZehDgEYISY/k9KAQBRSgEAbygAh2gAAAAAiEgAAAEAt/ABAAAAF5AAAAAAAAAA
+AAAAaAEAAAAAAAAZGAAAD4QIBxGEmP4VxgUAAQgHBl6ECAdghJj+T0oEAFFKBABeSgQAbygA
+h2gAAAAAiEgAAAEAbwABAAAAF5AAAAAAAAAAAAAAaAEAAAAAAAAVGAAAD4TYCRGEmP4VxgUA
+AdgJBl6E2AlghJj+T0oFAFFKBQBvKACHaAAAAACISAAAAQCn8AEAAAAXkAAAAAAAAAAAAABo
+AQAAAAAAABUYAAAPhKgMEYSY/hXGBQABqAwGXoSoDGCEmP5PSgEAUUoBAG8oAIdoAAAAAIhI
+AAABALfwAQAAABeQAAAAAAAAAAAAAGgBAAAAAAAAGRgAAA+EeA8RhJj+FcYFAAF4DwZehHgP
+YISY/k9KBABRSgQAXkoEAG8oAIdoAAAAAIhIAAABAG8AAQAAABeQAAAAAAAAAAAAAGgBAAAA
+AAAAFRgAAA+ESBIRhJj+FcYFAAFIEgZehEgSYISY/k9KBQBRSgUAbygAh2gAAAAAiEgAAAEA
+p/ABAAAAF5AAAAAAAAAAAAAAaAEAAAAAAAAVGAAAD4QYFRGEmP4VxgUAARgVBl6EGBVghJj+
+T0oBAFFKAQBvKACHaAAAAACISAAAAQC38AEAAAAXkAAAAAAAAAAAAABoAQAAAAAAABkYAAAP
+hOgXEYSY/hXGBQAB6BcGXoToF2CEmP5PSgQAUUoEAF5KBABvKACHaAAAAACISAAAAQBvAAEA
+AAAXkAAAAAAAAAAAAABoAQAAAAAAABUYAAAPhLgaEYSY/hXGBQABuBoGXoS4GmCEmP5PSgUA
+UUoFAG8oAIdoAAAAAIhIAAABAKfwBAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAxgAAA+E0AIR
+hDD9FcYFAAHQAgZehNACYIQw/W8oAAEAAAABAAAAAAABAwAAAAAAAAAAAAAAAAAAAAADGAAA
+D4TQAhGEMP0VxgUAAdACBl6E0AJghDD9bygAAwAAAC4AAQABAAAAAAABAwUAAAAAAAAAAAAA
+AAAAAAADGAAAD4TQAhGEMP0VxgUAAdACBl6E0AJghDD9bygABQAAAC4AAQAuAAIAAQAAAAAA
+AQMFBwAAAAAAAAAAAAAAAAAAAxgAAA+EOAQRhMj7FcYFAAE4BAZehDgEYITI+28oAAcAAAAu
+AAEALgACAC4AAwABAAAAAAABAwUHCQAAAAAAAAAAAAAAAAADGAAAD4Q4BBGEyPsVxgUAATgE
+Bl6EOARghMj7bygACQAAAC4AAQAuAAIALgADAC4ABAABAAAAAAABAwUHCQsAAAAAAAAAAAAA
+AAADGAAAD4SgBRGEYPoVxgUAAaAFBl6EoAVghGD6bygACwAAAC4AAQAuAAIALgADAC4ABAAu
+AAUAAQAAAAAAAQMFBwkLDQAAAAAAAAAAAAAAAxgAAA+EoAURhGD6FcYFAAGgBQZehKAFYIRg
++m8oAA0AAAAuAAEALgACAC4AAwAuAAQALgAFAC4ABgABAAAAAAABAwUHCQsNDwAAAAAAAAAA
+AAADGAAAD4QIBxGE+PgVxgUAAQgHBl6ECAdghPj4bygADwAAAC4AAQAuAAIALgADAC4ABAAu
+AAUALgAGAC4ABwABAAAAAAABAwUHCQsNDxEAAAAAAAAAAAADGAAAD4QIBxGE+PgVxgUAAQgH
+Bl6ECAdghPj4bygAEQAAAC4AAQAuAAIALgADAC4ABAAuAAUALgAGAC4ABwAuAAgACQAAAAAA
+AQAAAAAAAAAAAAAAAAAAAAAAAxgAAA+E0AIRhDD9FcYFAAHQAgZehNACYIQw/W8oAAIAAAAu
+AAEAAAAABAEDAAAAAAAAAAAAAAAAAAAAAAMYAAAPhNACEYQw/RXGBQAB0AIGXoTQAmCEMP1v
+KAADAAAALgABAAEAAAAABAEDBQAAAAAAAAAAAAAAAAAAAAMYAAAPhNACEYQw/RXGBQAB0AIG
+XoTQAmCEMP1vKAAFAAAALgABAC4AAgABAAAAAAQBAwUHAAAAAAAAAAAAAAAAAAADGAAAD4Q4
+BBGEyPsVxgUAATgEBl6EOARghMj7bygABwAAAC4AAQAuAAIALgADAAEAAAAABAEDBQcJAAAA
+AAAAAAAAAAAAAAMYAAAPhDgEEYTI+xXGBQABOAQGXoQ4BGCEyPtvKAAJAAAALgABAC4AAgAu
+AAMALgAEAAEAAAAABAEDBQcJCwAAAAAAAAAAAAAAAAMYAAAPhKAFEYRg+hXGBQABoAUGXoSg
+BWCEYPpvKAALAAAALgABAC4AAgAuAAMALgAEAC4ABQABAAAAAAQBAwUHCQsNAAAAAAAAAAAA
+AAADGAAAD4SgBRGEYPoVxgUAAaAFBl6EoAVghGD6bygADQAAAC4AAQAuAAIALgADAC4ABAAu
+AAUALgAGAAEAAAAABAEDBQcJCw0PAAAAAAAAAAAAAAMYAAAPhAgHEYT4+BXGBQABCAcGXoQI
+B2CE+PhvKAAPAAAALgABAC4AAgAuAAMALgAEAC4ABQAuAAYALgAHAAEAAAAABAEDBQcJCw0P
+EQAAAAAAAAAAAAMYAAAPhAgHEYT4+BXGBQABCAcGXoQIB2CE+PhvKAARAAAALgABAC4AAgAu
+AAMALgAEAC4ABQAuAAYALgAHAC4ACAABAAAAFwAAAAAAAAAAAAAAAAAAAAAAAAAVGAAAD4TQ
+AhGEmP4VxgUAAdACBl6E0AJghJj+T0oBAFFKAQBvKACHaAAAAACISAAAAQC38AEAAAAXgAAA
+AAAAAAAAAAAAAAAAAAAAABkYAAAPhKAFEYSY/hXGBQABoAUGXoSgBWCEmP5PSgQAUUoEAF5K
+BABvKACHaAAAAACISAAAAQBvAAEAAAAXgAAAAAAAAAAAAAAAAAAAAAAAABUYAAAPhHAIEYSY
+/hXGBQABcAgGXoRwCGCEmP5PSgUAUUoFAG8oAIdoAAAAAIhIAAABAKfwAQAAABeAAAAAAAAA
+AAAAAAAAAAAAAAAAFRgAAA+EQAsRhJj+FcYFAAFACwZehEALYISY/k9KAQBRSgEAbygAh2gA
+AAAAiEgAAAEAt/ABAAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAZGAAAD4QQDhGEmP4VxgUAARAO
+Bl6EEA5ghJj+T0oEAFFKBABeSgQAbygAh2gAAAAAiEgAAAEAbwABAAAAF4AAAAAAAAAAAAAA
+AAAAAAAAAAAVGAAAD4TgEBGEmP4VxgUAAeAQBl6E4BBghJj+T0oFAFFKBQBvKACHaAAAAACI
+SAAAAQCn8AEAAAAXgAAAAAAAAAAAAAAAAAAAAAAAABUYAAAPhLATEYSY/hXGBQABsBMGXoSw
+E2CEmP5PSgEAUUoBAG8oAIdoAAAAAIhIAAABALfwAQAAABeAAAAAAAAAAAAAAAAAAAAAAAAA
+GRgAAA+EgBYRhJj+FcYFAAGAFgZehIAWYISY/k9KBABRSgQAXkoEAG8oAIdoAAAAAIhIAAAB
+AG8AAQAAABeAAAAAAAAAAAAAAAAAAAAAAAAAFRgAAA+EUBkRhJj+FcYFAAFQGQZehFAZYISY
+/k9KBQBRSgUAbygAh2gAAAAAiEgAAAEAp/AIAAAAAAABAAAAAAAAAAAAAAAAAAAAAAADGAAA
+D4TQAhGEMP0VxgUAAdACBl6E0AJghDD9bygAAgAAAC4AAQAAAAAEAQMAAAAAAAAAAAAAAAAA
+AAAAAxgAAA+E0AIRhDD9FcYFAAHQAgZehNACYIQw/W8oAAMAAAAuAAEAAQAAABYEAQMFAAAA
+AAAAAAAAAAAAAAAAAxgAAA+E0AIRhDD9FcYFAAHQAgZehNACYIQw/W8oAAUAAAAuAAEALgAC
+AAEAAAAABAEDBQcAAAAAAAAAAAAAAAAAAAMYAAAPhDgEEYTI+xXGBQABOAQGXoQ4BGCEyPtv
+KAAHAAAALgABAC4AAgAuAAMAAQAAAAAEAQMFBwkAAAAAAAAAAAAAAAAAAxgAAA+EOAQRhMj7
+FcYFAAE4BAZehDgEYITI+28oAAkAAAAuAAEALgACAC4AAwAuAAQAAQAAAAAEAQMFBwkLAAAA
+AAAAAAAAAAAAAxgAAA+EoAURhGD6FcYFAAGgBQZehKAFYIRg+m8oAAsAAAAuAAEALgACAC4A
+AwAuAAQALgAFAAEAAAAABAEDBQcJCw0AAAAAAAAAAAAAAAMYAAAPhKAFEYRg+hXGBQABoAUG
+XoSgBWCEYPpvKAANAAAALgABAC4AAgAuAAMALgAEAC4ABQAuAAYAAQAAAAAEAQMFBwkLDQ8A
+AAAAAAAAAAAAAxgAAA+ECAcRhPj4FcYFAAEIBwZehAgHYIT4+G8oAA8AAAAuAAEALgACAC4A
+AwAuAAQALgAFAC4ABgAuAAcAAQAAAAAEAQMFBwkLDQ8RAAAAAAAAAAAAAxgAAA+ECAcRhPj4
+FcYFAAEIBwZehAgHYIT4+G8oABEAAAAuAAEALgACAC4AAwAuAAQALgAFAC4ABgAuAAcALgAI
+AAEAAAAXEAAAAAAAAAAAAABoAQAAAAAAABUYAAAPhDgEEYSY/hXGBQABOAQGXoQ4BGCEmP5P
+SgEAUUoBAG8oAIdoAAAAAIhIAAABALfwAQAAABeQAAAAAAAAAAAAAGgBAAAAAAAAGRgAAA+E
+CAcRhJj+FcYFAAEIBwZehAgHYISY/k9KBABRSgQAXkoEAG8oAIdoAAAAAIhIAAABAG8AAQAA
+ABeQAAAAAAAAAAAAAGgBAAAAAAAAFRgAAA+E2AkRhJj+FcYFAAHYCQZehNgJYISY/k9KBQBR
+SgUAbygAh2gAAAAAiEgAAAEAp/ABAAAAF5AAAAAAAAAAAAAAaAEAAAAAAAAVGAAAD4SoDBGE
+mP4VxgUAAagMBl6EqAxghJj+T0oBAFFKAQBvKACHaAAAAACISAAAAQC38AEAAAAXkAAAAAAA
+AAAAAABoAQAAAAAAABkYAAAPhHgPEYSY/hXGBQABeA8GXoR4D2CEmP5PSgQAUUoEAF5KBABv
+KACHaAAAAACISAAAAQBvAAEAAAAXkAAAAAAAAAAAAABoAQAAAAAAABUYAAAPhEgSEYSY/hXG
+BQABSBIGXoRIEmCEmP5PSgUAUUoFAG8oAIdoAAAAAIhIAAABAKfwAQAAABeQAAAAAAAAAAAA
+AGgBAAAAAAAAFRgAAA+EGBURhJj+FcYFAAEYFQZehBgVYISY/k9KAQBRSgEAbygAh2gAAAAA
+iEgAAAEAt/ABAAAAF5AAAAAAAAAAAAAAaAEAAAAAAAAZGAAAD4ToFxGEmP4VxgUAAegXBl6E
+6BdghJj+T0oEAFFKBABeSgQAbygAh2gAAAAAiEgAAAEAbwABAAAAF5AAAAAAAAAAAAAAaAEA
+AAAAAAAVGAAAD4S4GhGEmP4VxgUAAbgaBl6EuBpghJj+T0oFAFFKBQBvKACHaAAAAACISAAA
+AQCn8A0AAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAMYAAAPhGgBEYSY/hXGBQABaAEGXoRoAWCE
+mP5vKAABAAAAAQAAAAAAAQMAAAAAAAAAAAAAAAAAAAAAAxgAAA+EaAERhJj+FcYFAAFoAQZe
+hGgBYISY/m8oAAMAAAAuAAEAAQAAAAAAAQMFAAAAAAAAAAAAAAAAAAAAAxgAAA+E0AIRhDD9
+FcYFAAHQAgZehNACYIQw/W8oAAUAAAAuAAEALgACAAEAAAAAAAEDBQcAAAAAAAAAAAAAAAAA
+AAMYAAAPhDgEEYTI+xXGBQABOAQGXoQ4BGCEyPtvKAAHAAAALgABAC4AAgAuAAMAAQAAAAAA
+AQMFBwkAAAAAAAAAAAAAAAAAAxgAAA+EOAQRhMj7FcYFAAE4BAZehDgEYITI+28oAAkAAAAu
+AAEALgACAC4AAwAuAAQAAQAAAAAAAQMFBwkLAAAAAAAAAAAAAAAAAxgAAA+EoAURhGD6FcYF
+AAGgBQZehKAFYIRg+m8oAAsAAAAuAAEALgACAC4AAwAuAAQALgAFAAEAAAAAAAEDBQcJCw0A
+AAAAAAAAAAAAAAMYAAAPhKAFEYRg+hXGBQABoAUGXoSgBWCEYPpvKAANAAAALgABAC4AAgAu
+AAMALgAEAC4ABQAuAAYAAQAAAAAAAQMFBwkLDQ8AAAAAAAAAAAAAAxgAAA+ECAcRhPj4FcYF
+AAEIBwZehAgHYIT4+G8oAA8AAAAuAAEALgACAC4AAwAuAAQALgAFAC4ABgAuAAcAAQAAAAAA
+AQMFBwkLDQ8RAAAAAAAAAAAAAxgAAA+ECAcRhPj4FcYFAAEIBwZehAgHYIT4+G8oABEAAAAu
+AAEALgACAC4AAwAuAAQALgAFAC4ABgAuAAcALgAIAAEAAAAAEAEAAAAAAAAAAABoAQAAAAAA
+AAoYAAAPhNACEYSY/hXGBQAB0AIGXoTQAmCEmP6HaAAAAACISAAAAgAAAC4AAQAAAASQAQAA
+AAAAAAAAAGgBAAAAAAAAChgAAA+EoAURhJj+FcYFAAGgBQZehKAFYISY/odoAAAAAIhIAAAC
+AAEALgABAAAAApIBAAAAAAAAAAAAaAEAAAAAAAAKGAAAD4RwCBGETP8VxgUAAXAIBl6EcAhg
+hEz/h2gAAAAAiEgAAAIAAgAuAAEAAAAAkAEAAAAAAAAAAABoAQAAAAAAAAoYAAAPhEALEYSY
+/hXGBQABQAsGXoRAC2CEmP6HaAAAAACISAAAAgADAC4AAQAAAASQAQAAAAAAAAAAAGgBAAAA
+AAAAChgAAA+EEA4RhJj+FcYFAAEQDgZehBAOYISY/odoAAAAAIhIAAACAAQALgABAAAAApIB
+AAAAAAAAAAAAaAEAAAAAAAAKGAAAD4TgEBGETP8VxgUAAeAQBl6E4BBghEz/h2gAAAAAiEgA
+AAIABQAuAAEAAAAAkAEAAAAAAAAAAABoAQAAAAAAAAoYAAAPhLATEYSY/hXGBQABsBMGXoSw
+E2CEmP6HaAAAAACISAAAAgAGAC4AAQAAAASQAQAAAAAAAAAAAGgBAAAAAAAAChgAAA+EgBYR
+hJj+FcYFAAGAFgZehIAWYISY/odoAAAAAIhIAAACAAcALgABAAAAApIBAAAAAAAAAAAAaAEA
+AAAAAAAKGAAAD4RQGRGETP8VxgUAAVAZBl6EUBlghEz/h2gAAAAAiEgAAAIACAAuAAwAAAAd
+IlpGAAAAAAAAAAAAAAAA/CD5bgAAAAAAAAAAAAAAAI9QPBkAAAAAAAAAAAAAAABXAQh8AAAA
+AAAAAAAAAAAAqi3CPwAAAAAAAAAAAAAAANFR/igAAAAAAAAAAAAAAABaVvdMAAAAAAAAAAAA
+AAAAWynldQAAAAAAAAAAAAAAAIkjDHkAAAAAAAAAAAAAAABAVUYkAAAAAAAAAAAAAAAAif//
+/wAAAAAAAAAAAAAAAEAMIxkAAAAAAAAAAAAAAAD/////////////////////////////////
+/////////////////////////////////wwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/
+/wwAAAAAABIAgkaYogMACQgFAAkIAQAJCAMACQgFAAkIAQAJCAMACQgFAAkIAAAAABIAAQAJ
+CAMACQgFAAkIAQAJCAMACQgFAAkIAQAJCAMACQgFAAkIAAAAABIAAQAJCAMACQgFAAkIAQAJ
+CAMACQgFAAkIAQAJCAMACQgFAAkIAAASAAEACQgDAAkIBQAJCAEACQgDAAkIBQAJCAEACQgD
+AAkIBQAJCAAAEgAPAAkIGQAJCBsACQgPAAkIGQAJCBsACQgPAAkIGQAJCBsACQgBAMwlPCwA
+AAAAAAAAAAABAgACAM8BAAAEAAAACAAAAOUAAAAAAAAAzgEAAApBAABhbgAALG8AAI8/AQA3
+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
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7/AAAF
+AQIAAAAAAAAAAAAAAAAAAAAAAAEAAADghZ/y+U9oEKuRCAArJ7PZMAAAAMQBAAASAAAAAQAA
+AJgAAAACAAAAoAAAAAMAAADgAAAABAAAAOwAAAAFAAAAEAEAAAYAAAAcAQAABwAAACgBAAAI
+AAAAOAEAAAkAAABIAQAAEgAAAFQBAAAKAAAAdAEAAAsAAACAAQAADAAAAIwBAAANAAAAmAEA
+AA4AAACkAQAADwAAAKwBAAAQAAAAtAEAABMAAAC8AQAAAgAAAOQEAAAeAAAAOAAAAE1pbnV0
+ZXMgb2YgRCBEaXZpc2lvbiBTZW5pb3IgTWFuYWdlbWVudCBUZWFtIE1lZXRpbmcAAAAAHgAA
+AAQAAAAAAAAAHgAAABwAAABIdW1iZXJzaWRlIFBvbGljZSBBdXRob3JpdHkAHgAAAAQAAAAA
+AAAAHgAAAAQAAAAAAAAAHgAAAAgAAABOb3JtYWwAAB4AAAAIAAAAODA2NQAAAAAeAAAABAAA
+ADIAAAAeAAAAGAAAAE1pY3Jvc29mdCBPZmZpY2UgV29yZAAAAEAAAAAA0klrAAAAAEAAAAAA
+nFcKpaTFAUAAAAAAyAJbT3PKAUAAAAAAyAJbT3PKAQMAAAABAAAAAwAAAEIHAAADAAAAZSkA
+AAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/wAABQECAAAAAAAA
+AAAAAAAAAAAAAAACAAAAAtXN1ZwuGxCTlwgAKyz5rkQAAAAF1c3VnC4bEJOXCAArLPmueAEA
+ADQBAAAMAAAAAQAAAGgAAAAPAAAAcAAAAAUAAACUAAAABgAAAJwAAAARAAAApAAAABcAAACs
+AAAACwAAALQAAAAQAAAAvAAAABMAAADEAAAAFgAAAMwAAAANAAAA1AAAAAwAAAAVAQAAAgAA
+AOQEAAAeAAAAHAAAAEh1bWJlcnNpZGUgUG9saWNlIEF1dGhvcml0eQADAAAAWAAAAAMAAAAY
+AAAAAwAAAI8wAAADAAAAqBkLAAsAAAAAAAAACwAAAAAAAAALAAAAAAAAAAsAAAAAAAAAHhAA
+AAEAAAA1AAAATWludXRlcyBvZiBEIERpdmlzaW9uIFNlbmlvciBNYW5hZ2VtZW50IFRlYW0g
+TWVldGluZwAMEAAAAgAAAB4AAAAGAAAAVGl0bGUAAwAAAAEAAAAAzAUAABMAAAAAAAAAoAAA
+AAEAAAA1AgAAAgAAAD0CAAADAAAAqQIAAAQAAADJAgAABQAAAOECAAAGAAAA/QIAAAcAAAAl
+AwAACAAAAFEDAAAJAAAAeQMAAAoAAACVAwAACwAAAEkEAAAMAAAAVQQAAA0AAABxBAAADgAA
+AJkEAAAPAAAAxQQAABAAAADtBAAAEQAAAAkFAAASAAAAvQUAABEAAAACAAAACQAAAEZ1bGxO
+YW1lAAMAAAAiAAAAUHJvdGVjdGl2ZSBNYXJraW5nIENsYXNzaWZpY2F0aW9uAAQAAAAWAAAA
+QWRkaXRpb25hbCBEZXNjcmlwdG9yAAUAAAARAAAAQ3JlYXRvciBVc2VyTmFtZQAGAAAAEgAA
+AENyZWF0b3IgVXNlclRpdGxlAAcAAAATAAAAQ3JlYXRvciBPZmZpY2VOYW1lAAgAAAATAAAA
+Q3JlYXRvciBEZXBhcnRtZW50AAkAAAAQAAAAQ3JlYXRvciBDb21wYW55AAoAAAAXAAAAQ3Jl
+YXRvciBQcm94eUFkZHJlc3NlcwALAAAAEAAAAENyZWF0b3IgTWFuYWdlcgAMAAAACQAAAFVz
+ZXJOYW1lAA0AAAAKAAAAVXNlclRpdGxlAA4AAAALAAAAT2ZmaWNlTmFtZQAPAAAACwAAAERl
+cGFydG1lbnQAEAAAAAgAAABDb21wYW55ABEAAAAPAAAAUHJveHlBZGRyZXNzZXMAEgAAAAgA
+AABNYW5hZ2VyAAIAAADkBAAAHgAAAGQAAABDOlxEb2N1bWVudHMgYW5kIFNldHRpbmdzXDgw
+NjVcTG9jYWwgU2V0dGluZ3NcVGVtcG9yYXJ5IEludGVybmV0IEZpbGVzXE9MS0M0XENPRyBN
+aW5zIDE3IDggMDUuZG9jAAAAHgAAABgAAABOT1QgUFJPVEVDVElWRUxZIE1BUktFRAAeAAAA
+EAAAAE5PIERFU0NSSVBUT1IAAAAeAAAAFAAAAFdhbGxlciwgUGF1bCA4MDY1AAAAHgAAACAA
+AABJbmZvcm1hdGlvbiBDb21wbGlhbmNlIE9mZmljZXIAAB4AAAAkAAAASUNVIC0gRGF0YSBQ
+cm90ZWN0aW9uL0ZPSSAoQ0RCLU9TKQAAHgAAACAAAABDb3Jwb3JhdGUgRGV2ZWxvcG1lbnQg
+QnJhbmNoAAAAAB4AAAAUAAAASHVtYmVyc2lkZSBQb2xpY2UAAAAeAAAArAAAAENDTUFJTDpX
+YWxsZXIsIFBhdWwgYXQgTm9ydGhiYW5rfE1TOkhVTUJFUlNJREUvTk9SVEhCQU5LL1BBVUxX
+QUxMRVJ8U01UUDp4eHh4Lnh4eHh4eEB4eHh4eHh4eHh4Lnh4eC54eHh4eHgueHh8WDQwMDpj
+PUdCO2E9IDtwPUh1bWJlcnNpZGUgUG9saWM7bz1Ob3J0aGJhbms7cz1XYWxsZXI7Zz1QYXVs
+OwAeAAAABAAAAAAAAAAeAAAAFAAAAFdhbGxlciwgUGF1bCA4MDY1AAAAHgAAACAAAABJbmZv
+cm1hdGlvbiBDb21wbGlhbmNlIE9mZmljZXIAAB4AAAAkAAAASUNVIC0gRGF0YSBQcm90ZWN0
+aW9uL0ZPSSAoQ0RCLU9TKQAAHgAAACAAAABDb3Jwb3JhdGUgRGV2ZWxvcG1lbnQgQnJhbmNo
+AAAAAB4AAAAUAAAASHVtYmVyc2lkZSBQb2xpY2UAAAAeAAAArAAAAENDTUFJTDpXYWxsZXIs
+IFBhdWwgYXQgTm9ydGhiYW5rfE1TOkhVTUJFUlNJREUvTk9SVEhCQU5LL1BBVUxXQUxMRVJ8
+U01UUDp4eHh4Lnh4eHh4eEB4eHh4eHh4eHh4Lnh4eC54eHh4eHgueHh8WDQwMDpjPUdCO2E9
+IDtwPUh1bWJlcnNpZGUgUG9saWM7bz1Ob3J0aGJhbms7cz1XYWxsZXI7Zz1QYXVsOwAeAAAA
+BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAADAAAABAAAAAUAAAAG
+AAAABwAAAAgAAAAJAAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAA
+ABQAAAAVAAAAFgAAABcAAAAYAAAAGQAAABoAAAAbAAAAHAAAAB0AAAAeAAAAHwAAACAAAAAh
+AAAAIgAAACMAAAAkAAAAJQAAACYAAAAnAAAAKAAAACkAAAAqAAAAKwAAACwAAAAtAAAALgAA
+AP7///8wAAAAMQAAADIAAAAzAAAANAAAADUAAAA2AAAA/v///zgAAAA5AAAAOgAAADsAAAA8
+AAAAPQAAAD4AAAA/AAAAQAAAAEEAAABCAAAAQwAAAEQAAABFAAAARgAAAEcAAABIAAAASQAA
+AEoAAABLAAAATAAAAE0AAABOAAAATwAAAFAAAABRAAAAUgAAAFMAAABUAAAAVQAAAFYAAABX
+AAAAWAAAAFkAAABaAAAAWwAAAFwAAABdAAAAXgAAAF8AAABgAAAA/v///2IAAABjAAAAZAAA
+AGUAAABmAAAAZwAAAGgAAAD+////agAAAGsAAABsAAAAbQAAAG4AAABvAAAAcAAAAP7////9
+////cwAAAP7////+/////v//////////////////////////////////////////////////
+//////9SAG8AbwB0ACAARQBuAHQAcgB5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAFgAFAf//////////AwAAAAYJAgAAAAAAwAAAAAAAAEYAAAAAAAAA
+AAAAAACAnPtsT3PKAXUAAACAAAAAAAAAAEQAYQB0AGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAIB////////////////AAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALwAAAAAQAAAAAAAAMQBUAGEAYgBs
+AGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AA4AAgEBAAAABgAAAP////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3
+AAAAFVIAAAAAAABXAG8AcgBkAEQAbwBjAHUAbQBlAG4AdAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAGgACAQIAAAAFAAAA/////wAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqXAAAAAAAAAUAUwB1AG0AbQBhAHIAeQBJAG4AZgBv
+AHIAbQBhAHQAaQBvAG4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAIB////////////
+////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYQAAAAAQAAAAAAAABQBE
+AG8AYwB1AG0AZQBuAHQAUwB1AG0AbQBhAHIAeQBJAG4AZgBvAHIAbQBhAHQAaQBvAG4AAAAA
+AAAAAAAAADgAAgEEAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAABpAAAAABAAAAAAAAABAEMAbwBtAHAATwBiAGoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgACAP///////////////wAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////
+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAQAAAP7/////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////8BAP7/AwoAAP////8GCQIAAAAAAMAAAAAA
+AABGHwAAAE1pY3Jvc29mdCBPZmZpY2UgV29yZCBEb2N1bWVudAAKAAAATVNXb3JkRG9jABAA
+AABXb3JkLkRvY3VtZW50LjgA9DmycQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AFIAbwBvAHQAIABFAG4AdAByAHkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAWAAUB//////////8DAAAABgkCAAAAAADAAAAAAAAARgAAAAAAAAAAAAAA
+AKBIjeIMdMoBfQAAAAAIAAAAAAAARABhAHQAYQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAgH///////////////8AAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvAAAAABAAAAAAAAAxAFQAYQBiAGwAZQAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAC
+AQEAAAAGAAAA/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAAV
+UgAAAAAAAFcAbwByAGQARABvAGMAdQBtAGUAbgB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAaAAIBAgAAAAUAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAACpcAAAAAAAAAQAAAAIAAAADAAAABAAAAAUAAAAGAAAABwAA
+AAgAAAAJAAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAV
+AAAAFgAAABcAAAAYAAAAGQAAABoAAAAbAAAAHAAAAB0AAAAeAAAAHwAAACAAAAAhAAAAIgAA
+ACMAAAAkAAAAJQAAACYAAAAnAAAAKAAAACkAAAAqAAAAKwAAACwAAAAtAAAALgAAAP7///8w
+AAAAMQAAADIAAAAzAAAANAAAADUAAAA2AAAA/v///zgAAAA5AAAAOgAAADsAAAA8AAAAPQAA
+AD4AAAA/AAAAQAAAAEEAAABCAAAAQwAAAEQAAABFAAAARgAAAEcAAABIAAAASQAAAEoAAABL
+AAAATAAAAE0AAABOAAAATwAAAFAAAABRAAAAUgAAAFMAAABUAAAAVQAAAFYAAABXAAAAWAAA
+AFkAAABaAAAAWwAAAFwAAABdAAAAXgAAAF8AAABgAAAA/v///2IAAABjAAAAZAAAAGUAAABm
+AAAAZwAAAGgAAAD+////////////////////////////////////////////////////////
+/////////////////3wAAAD9/////v///3oAAAB7AAAA/v////7///95AAAA//////////8B
+AAAA/v///wMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAA
+AA8AAAAQAAAAEQAAABIAAAATAAAAFAAAABUAAAAWAAAAFwAAABgAAAAZAAAAGgAAABsAAAAc
+AAAAHQAAAB4AAAAfAAAA/v//////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+/////////////////////////////////wAAAACgAAAAAQAAADgCAAACAAAAQAIAAAMAAACs
+AgAABAAAAMwCAAAFAAAA5AIAAAYAAAAAAwAABwAAACgDAAAIAAAAVAMAAAkAAAB8AwAACgAA
+AJgDAAALAAAATAQAAAwAAABYBAAADQAAAHQEAAAOAAAAnAQAAA8AAADIBAAAEAAAAPAEAAAR
+AAAADAUAABIAAADABQAAEQAAAAIAAAAJAAAARnVsbE5hbWUAAwAAACIAAABQcm90ZWN0aXZl
+IE1hcmtpbmcgQ2xhc3NpZmljYXRpb24ABAAAABYAAABBZGRpdGlvbmFsIERlc2NyaXB0b3IA
+BQAAABEAAABDcmVhdG9yIFVzZXJOYW1lAAYAAAASAAAAQ3JlYXRvciBVc2VyVGl0bGUABwAA
+ABMAAABDcmVhdG9yIE9mZmljZU5hbWUACAAAABMAAABDcmVhdG9yIERlcGFydG1lbnQACQAA
+ABAAAABDcmVhdG9yIENvbXBhbnkACgAAABcAAABDcmVhdG9yIFByb3h5QWRkcmVzc2VzAAsA
+AAAQAAAAQ3JlYXRvciBNYW5hZ2VyAAwAAAAJAAAAVXNlck5hbWUADQAAAAoAAABVc2VyVGl0
+bGUADgAAAAsAAABPZmZpY2VOYW1lAA8AAAALAAAARGVwYXJ0bWVudAAQAAAACAAAAENvbXBh
+bnkAEQAAAA8AAABQcm94eUFkZHJlc3NlcwASAAAACAAAAE1hbmFnZXIAAgAAAgAAAOQEAAAe
+AAAAZAAAAEM6XERvY3VtZW50cyBhbmQgU2V0dGluZ3NcODA2NVxMb2NhbCBTZXR0aW5nc1xU
+ZW1wb3JhcnkgSW50ZXJuZXQgRmlsZXNcT0xLQzRcQ09HIE1pbnMgMTcgOCAwNS5kb2MAAAAe
+AAAAGAAAAE5PVCBQUk9URUNUSVZFTFkgTUFSS0VEAB4AAAAQAAAATk8gREVTQ1JJUFRPUgAA
+AB4AAAAUAAAAV2FsbGVyLCBQYXVsIDgwNjUAAAAeAAAAIAAAAEluZm9ybWF0aW9uIENvbXBs
+aWFuY2UgT2ZmaWNlcgAAHgAAACQAAABJQ1UgLSBEYXRhIFByb3RlY3Rpb24vRk9JIChDREIt
+T1MpAAAeAAAAIAAAAENvcnBvcmF0ZSBEZXZlbG9wbWVudCBCcmFuY2gAAAAAHgAAABQAAABI
+dW1iZXJzaWRlIFBvbGljZQAAAB4AAACsAAAAQ0NNQUlMOldhbGxlciwgUGF1bCBhdCBOb3J0
+aGJhbmt8TVM6SFVNQkVSU0lERS9OT1JUSEJBTksvUEFVTFdBTExFUnxTTVRQOnh4eHgueHh4
+eHh4QHh4eHh4eHh4eHgueHh4Lnh4eHh4eC54eHxYNDAwOmM9R0I7YT0gO3A9SHVtYmVyc2lk
+ZSBQb2xpYztvPU5vcnRoYmFuaztzPVdhbGxlcjtnPVBhdWw7AB4AAAAEAAAAAAAAAB4AAAAU
+AAAAV2FsbGVyLCBQYXVsIDgwNjUAAAAeAAAAIAAAAEluZm9ybWF0aW9uIENvbXBsaWFuY2Ug
+T2ZmaWNlcgAAHgAAACQAAABJQ1UgLSBEYXRhIFByb3RlY3Rpb24vRk9JIChDREItT1MpAAAe
+AAAAIAAAAENvcnBvcmF0ZSBEZXZlbG9wbWVudCBCcmFuY2gAAAAAHgAAABQAAABIdW1iZXJz
+aWRlIFBvbGljZQAAAB4AAACsAAAAQ0NNQUlMOldhbGxlciwgUGF1bCBhdCBOb3J0aGJhbmt8
+TVM6SFVNQkVSU0lERS9OT1JUSEJBTksvUEFVTFdBTExFUnxTTVRQOnh4eHgueHh4eHh4QHh4
+eHh4eHh4eHgueHh4Lnh4eHh4eC54eHxYNDAwOmM9R0I7YT0gO3A9SHVtYmVyc2lkZSBQb2xp
+YztvPU5vcnRoYmFuaztzPVdhbGxlcjtnPVBhdWw7AB4AAAAEAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAUwB1
+AG0AbQBhAHIAeQBJAG4AZgBvAHIAbQBhAHQAaQBvAG4AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAoAAIB////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAYQAAAAAQAAAAAAAABQBEAG8AYwB1AG0AZQBuAHQAUwB1AG0AbQBhAHIAeQBJAG4AZgBv
+AHIAbQBhAHQAaQBvAG4AAAAAAAAAAAAAADgAAgEEAAAA//////////8AAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAARAcAAAAAAAABAEMAbwBtAHAATwBiAGoAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgACAP//////
+/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAA////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQD+/wMKAAD/////BgkCAAAAAADAAAAAAAAARh8AAABN
+aWNyb3NvZnQgT2ZmaWNlIFdvcmQgRG9jdW1lbnQACgAAAE1TV29yZERvYwAQAAAAV29yZC5E
+b2N1bWVudC44APQ5snEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/wAABQECAAAAAAAA
+AAAAAAAAAAAAAAACAAAAAtXN1ZwuGxCTlwgAKyz5rkQAAAAF1c3VnC4bEJOXCAArLPmueAEA
+ADQBAAAMAAAAAQAAAGgAAAAPAAAAcAAAAAUAAACUAAAABgAAAJwAAAARAAAApAAAABcAAACs
+AAAACwAAALQAAAAQAAAAvAAAABMAAADEAAAAFgAAAMwAAAANAAAA1AAAAAwAAAAVAQAAAgAA
+AOQEAAAeAAAAHAAAAEh1bWJlcnNpZGUgUG9saWNlIEF1dGhvcml0eQADAAAAWAAAAAMAAAAY
+AAAAAwAAAI8wAAADAAAAqBkLAAsAAAAAAAAACwAAAAAAAAALAAAAAAAAAAsAAAAAAAAAHhAA
+AAEAAAA1AAAATWludXRlcyBvZiBEIERpdmlzaW9uIFNlbmlvciBNYW5hZ2VtZW50IFRlYW0g
+TWVldGluZwAMEAAAAgAAAB4AAAAGAAAAVGl0bGUAAwAAAAEAAAAAzAUAABMAAAC5ngIFkAYA
+IAEAABUAAAADACAOEgwBAAMAIQ4AAAAAAwD3DwAAAAACAfkPAQAAABAAAAACb0HphbqxSbh3
+RkiD0BxfHgABMAEAAAAUAAAARk9JIDA5LTAyOTc2aWlpLmRvYwBAAAcwgNRLbE9zygFAAAgw
+sgJfDA10ygECAQI3AQAAAAAAAAAeAAM3AQAAAAUAAAAuZG9jAAAAAAMABTcBAAAAHgAHNwEA
+AAAUAAAARk9JIDA5LTAyOTc2aWlpLmRvYwAeAAg3AQAAAAEAAAAAAAAAAwALN/////8eAA03
+AQAAAAEAAAAAAAAAAwAUNwAAAAADAPp/AAAAAEAA+38AQN2jV0WzDEAA/H8AQN2jV0WzDAMA
+/X8AAAAACwD+fwAAAAALAP9/AAAAAKAz
+
+--zhXaljGHf11kAtnf--
diff --git a/spec/fixtures/psni.pdf b/spec/fixtures/psni.pdf
new file mode 100644
index 000000000..daca3f4ec
--- /dev/null
+++ b/spec/fixtures/psni.pdf
Binary files differ
diff --git a/spec/fixtures/raw_emails.yml b/spec/fixtures/raw_emails.yml
index 4930e9966..d24b61854 100644
--- a/spec/fixtures/raw_emails.yml
+++ b/spec/fixtures/raw_emails.yml
@@ -6,7 +6,7 @@ useless_raw_email:
Date: Tue, 13 Nov 2007 11:39:55 +0000
NoCc: foi+request-1-4b571715@cat
Bcc:
- Subject: Re: Freedom of Information Request - Why do you have such a fancy dog?
+ Subject: Geraldine FOI Code AZXB421
Reply-To:
In-Reply-To: <471f1eae5d1cb_7347..fdbe67386163@cat.tmail>
diff --git a/spec/models/incoming_message_spec.rb b/spec/models/incoming_message_spec.rb
index f08f1338c..f41dff06d 100644
--- a/spec/models/incoming_message_spec.rb
+++ b/spec/models/incoming_message_spec.rb
@@ -109,7 +109,7 @@ describe IncomingMessage, " checking validity to reply to" do
end
describe IncomingMessage, " when censoring data" do
- fixtures :incoming_messages, :raw_emails
+ fixtures :incoming_messages, :raw_emails, :public_bodies, :info_requests
before do
@test_data = "There was a mouse called Stilton, he wished that he was blue."
@@ -157,6 +157,9 @@ describe IncomingMessage, " when censoring data" do
data.should == "His email was x\000x\000x\000@\000x\000x\000x\000.\000x\000x\000x\000, indeed"
end
+ # As at March 9th 2010: This test fails with pdftk 1.41+dfsg-1 installed
+ # which is in Ubuntu Karmic. It works again for the lasest version
+ # 1.41+dfsg-7 in Debian unstable. And it works for Debian stable.
it "should replace everything in PDF files" do
orig_pdf = load_file_fixture('tfl.pdf')
pdf = orig_pdf.dup
@@ -171,6 +174,13 @@ describe IncomingMessage, " when censoring data" do
masked_text.should match(/xxx@xxx.xxx.xx/)
end
+ it "should not produce zero length output if pdftk silently fails" do
+ orig_pdf = load_file_fixture('psni.pdf')
+ pdf = orig_pdf.dup
+ @im.binary_mask_stuff!(pdf, "application/pdf")
+ pdf.should_not == ""
+ end
+
it "should apply censor rules to HTML files" do
data = @test_data.dup
@im.html_mask_stuff!(data)
@@ -228,4 +238,60 @@ describe IncomingMessage, " when uudecoding bad messages" do
end
+describe IncomingMessage, "when messages are attached to messages" do
+ it "should flatten all the attachments out" do
+ mail_body = load_file_fixture('incoming-request-attach-attachments.email')
+ mail = TMail::Mail.parse(mail_body)
+ mail.base64_decode
+
+ im = IncomingMessage.new
+ im.stub!(:mail).and_return(mail)
+ ir = InfoRequest.new
+ im.info_request = ir
+
+ attachments = im.get_attachments_for_display
+ attachments.size.should == 3
+ attachments[0].display_filename.should == 'Same attachment twice.txt'
+ attachments[1].display_filename.should == 'hello.txt'
+ attachments[2].display_filename.should == 'hello.txt'
+ end
+end
+
+describe IncomingMessage, "when Outlook messages are attached to messages" do
+ it "should flatten all the attachments out" do
+ mail_body = load_file_fixture('incoming-request-oft-attachments.email')
+ mail = TMail::Mail.parse(mail_body)
+ mail.base64_decode
+
+ im = IncomingMessage.new
+ im.stub!(:mail).and_return(mail)
+ ir = InfoRequest.new
+ im.info_request = ir
+
+ attachments = im.get_attachments_for_display
+ attachments.size.should == 2
+ attachments[0].display_filename.should == 'test.html' # picks HTML rather than text by default, as likely to render better
+ attachments[1].display_filename.should == 'attach.txt'
+ end
+end
+
+describe IncomingMessage, "when TNEF attachments are attached to messages" do
+ it "should flatten all the attachments out" do
+ mail_body = load_file_fixture('incoming-request-tnef-attachments.email')
+ mail = TMail::Mail.parse(mail_body)
+ mail.base64_decode
+
+ im = IncomingMessage.new
+ im.stub!(:mail).and_return(mail)
+ ir = InfoRequest.new
+ im.info_request = ir
+
+ attachments = im.get_attachments_for_display
+ attachments.size.should == 2
+ attachments[0].display_filename.should == 'FOI 09 02976i.doc'
+ attachments[1].display_filename.should == 'FOI 09 02976iii.doc'
+ end
+end
+
+
diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb
index b16ced344..63ab31c53 100644
--- a/spec/models/info_request_spec.rb
+++ b/spec/models/info_request_spec.rb
@@ -110,38 +110,107 @@ describe InfoRequest do
it "should cope with indexing after item is deleted" do
rebuild_xapian_index
- verbose = false
# delete event from underneath indexing; shouldn't cause error
info_request_events(:useless_incoming_message_event).save!
info_request_events(:useless_incoming_message_event).destroy
- ActsAsXapian.update_index(true, verbose)
+ update_xapian_index
end
end
describe "when calculating the status" do
- fixtures :info_requests, :info_request_events, :holidays
+ fixtures :info_requests, :info_request_events, :holidays, :public_bodies
before do
@ir = info_requests(:naughty_chicken_request)
end
+ it "has expected sent date" do
+ @ir.last_event_forming_initial_request.outgoing_message.last_sent_at.strftime("%F").should == '2007-10-14'
+ end
+
it "has correct due date" do
@ir.date_response_required_by.strftime("%F").should == '2007-11-09'
end
- it "isn't overdue on due date" do
+ it "has correct very overdue after date" do
+ @ir.date_very_overdue_after.strftime("%F").should == '2007-12-10'
+ end
+
+ it "isn't overdue on due date (20 working days after request sent)" do
Time.stub!(:now).and_return(Time.utc(2007, 11, 9, 23, 59))
@ir.calculate_status.should == 'waiting_response'
end
- it "is overdue a day after due date " do
- Time.stub!(:now).and_return(Time.utc(2007, 11, 10))
+ it "is overdue a day after due date (20 working days after request sent)" do
+ Time.stub!(:now).and_return(Time.utc(2007, 11, 10, 00, 01))
@ir.calculate_status.should == 'waiting_response_overdue'
end
+
+ it "is still overdue 40 working days after request sent" do
+ Time.stub!(:now).and_return(Time.utc(2007, 12, 10, 23, 59))
+ @ir.calculate_status.should == 'waiting_response_overdue'
+ end
+
+ it "is very overdue the day after 40 working days after request sent" do
+ Time.stub!(:now).and_return(Time.utc(2007, 12, 11, 00, 01))
+ @ir.calculate_status.should == 'waiting_response_very_overdue'
+ end
end
-
+
+ describe "when calculating the status for a school" do
+ fixtures :info_requests, :info_request_events, :holidays, :public_bodies
+
+ before do
+ @ir = info_requests(:naughty_chicken_request)
+ @ir.public_body.tag_string = "school"
+ @ir.public_body.is_school?.should == true
+ end
+
+ it "has expected sent date" do
+ @ir.last_event_forming_initial_request.outgoing_message.last_sent_at.strftime("%F").should == '2007-10-14'
+ end
+
+ it "has correct due date" do
+ @ir.date_response_required_by.strftime("%F").should == '2007-11-09'
+ end
+
+ it "has correct very overdue after date" do
+ @ir.date_very_overdue_after.strftime("%F").should == '2008-01-11' # 60 working days for schools
+ end
+
+ it "isn't overdue on due date (20 working days after request sent)" do
+ Time.stub!(:now).and_return(Time.utc(2007, 11, 9, 23, 59))
+ @ir.calculate_status.should == 'waiting_response'
+ end
+
+ it "is overdue a day after due date (20 working days after request sent)" do
+ Time.stub!(:now).and_return(Time.utc(2007, 11, 10, 00, 01))
+ @ir.calculate_status.should == 'waiting_response_overdue'
+ end
+
+ it "is still overdue 40 working days after request sent" do
+ Time.stub!(:now).and_return(Time.utc(2007, 12, 10, 23, 59))
+ @ir.calculate_status.should == 'waiting_response_overdue'
+ end
+
+ it "is still overdue the day after 40 working days after request sent" do
+ Time.stub!(:now).and_return(Time.utc(2007, 12, 11, 00, 01))
+ @ir.calculate_status.should == 'waiting_response_overdue'
+ end
+
+ it "is still overdue 60 working days after request sent" do
+ Time.stub!(:now).and_return(Time.utc(2008, 01, 11, 23, 59))
+ @ir.calculate_status.should == 'waiting_response_overdue'
+ end
+
+ it "is very overdue the day after 60 working days after request sent" do
+ Time.stub!(:now).and_return(Time.utc(2008, 01, 12, 00, 01))
+ @ir.calculate_status.should == 'waiting_response_very_overdue'
+ end
+ end
+
describe 'when asked if a user is the owning user for this request' do
before do
diff --git a/spec/models/outgoing_mailer_spec.rb b/spec/models/outgoing_mailer_spec.rb
index de59b09b2..eeed53251 100644
--- a/spec/models/outgoing_mailer_spec.rb
+++ b/spec/models/outgoing_mailer_spec.rb
@@ -66,4 +66,62 @@ describe OutgoingMailer, " when working out follow up addresses" do
end
+describe OutgoingMailer, "when working out follow up subjects" do
+ fixtures :info_requests, :incoming_messages, :outgoing_messages
+
+ it "should prefix the title with 'Freedom of Information request -' for initial requests" do
+ ir = info_requests(:fancy_dog_request)
+ im = ir.incoming_messages[0]
+
+ ir.email_subject_request.should == "Freedom of Information request - Why do you have & such a fancy dog?"
+ end
+
+ it "should use 'Re:' and inital request subject for followups which aren't replies to particular messages" do
+ ir = info_requests(:fancy_dog_request)
+ om = outgoing_messages(:useless_outgoing_message)
+
+ OutgoingMailer.subject_for_followup(ir, om).should == "Re: Freedom of Information request - Why do you have & such a fancy dog?"
+ end
+
+ it "should prefix with Re: the subject of the message being replied to" do
+ ir = info_requests(:fancy_dog_request)
+ im = ir.incoming_messages[0]
+ om = outgoing_messages(:useless_outgoing_message)
+ om.incoming_message_followup = im
+
+ OutgoingMailer.subject_for_followup(ir, om).should == "Re: Geraldine FOI Code AZXB421"
+ end
+
+ it "should not add Re: prefix if there already is such a prefix" do
+ ir = info_requests(:fancy_dog_request)
+ im = ir.incoming_messages[0]
+ om = outgoing_messages(:useless_outgoing_message)
+ om.incoming_message_followup = im
+
+ im.raw_email.data = im.raw_email.data.sub("Subject: Geraldine FOI Code AZXB421", "Subject: Re: Geraldine FOI Code AZXB421")
+ OutgoingMailer.subject_for_followup(ir, om).should == "Re: Geraldine FOI Code AZXB421"
+ end
+
+ it "should not add Re: prefix if there already is a lower case re: prefix" do
+ ir = info_requests(:fancy_dog_request)
+ im = ir.incoming_messages[0]
+ om = outgoing_messages(:useless_outgoing_message)
+ om.incoming_message_followup = im
+
+ im.raw_email.data = im.raw_email.data.sub("Subject: Geraldine FOI Code AZXB421", "Subject: re: Geraldine FOI Code AZXB421")
+ OutgoingMailer.subject_for_followup(ir, om).should == "re: Geraldine FOI Code AZXB421"
+ end
+
+ it "should use 'Re:' and initial request subject when replying to failed delivery notifications" do
+ ir = info_requests(:fancy_dog_request)
+ im = ir.incoming_messages[0]
+ om = outgoing_messages(:useless_outgoing_message)
+ om.incoming_message_followup = im
+
+ im.raw_email.data = im.raw_email.data.sub("foiperson@localhost", "postmaster@localhost")
+ im.raw_email.data = im.raw_email.data.sub("Subject: Geraldine FOI Code AZXB421", "Subject: Delivery Failed")
+ OutgoingMailer.subject_for_followup(ir, om).should == "Re: Freedom of Information request - Why do you have & such a fancy dog?"
+ end
+end
+
diff --git a/spec/models/outgoing_message_spec.rb b/spec/models/outgoing_message_spec.rb
index fedde6649..7e8dd4a1e 100644
--- a/spec/models/outgoing_message_spec.rb
+++ b/spec/models/outgoing_message_spec.rb
@@ -31,7 +31,7 @@ end
describe IncomingMessage, " when censoring data" do
- fixtures :outgoing_messages
+ fixtures :outgoing_messages, :info_requests
before do
@om = outgoing_messages(:useless_outgoing_message)
diff --git a/spec/models/public_body_spec.rb b/spec/models/public_body_spec.rb
index 827527213..5c58cdc54 100644
--- a/spec/models/public_body_spec.rb
+++ b/spec/models/public_body_spec.rb
@@ -85,3 +85,35 @@ describe PublicBody, "when searching" do
end
end
+describe PublicBody, " when loading CSV files" do
+ it "should do a dry run successfully" do
+ original_count = PublicBody.count
+
+ csv_contents = load_file_fixture("fake-authority-type.csv")
+ errors, notes = PublicBody.import_csv(csv_contents, 'fake', true, 'someadmin') # true means dry run
+ errors.should == []
+ notes.size.should == 3
+ notes.should == ["line 1: new authority 'North West Fake Authority' with email north_west_foi@localhost",
+ "line 2: new authority 'Scottish Fake Authority' with email scottish_foi@localhost",
+ "line 3: new authority 'Fake Authority of Northern Ireland' with email ni_foi@localhost"]
+
+ PublicBody.count.should == original_count
+ end
+
+ it "should do full run successfully" do
+ original_count = PublicBody.count
+
+ csv_contents = load_file_fixture("fake-authority-type.csv")
+ errors, notes = PublicBody.import_csv(csv_contents, 'fake', false, 'someadmin') # false means real run
+ errors.should == []
+ notes.size.should == 3
+ notes.should == ["line 1: new authority 'North West Fake Authority' with email north_west_foi@localhost",
+ "line 2: new authority 'Scottish Fake Authority' with email scottish_foi@localhost",
+ "line 3: new authority 'Fake Authority of Northern Ireland' with email ni_foi@localhost"]
+
+ PublicBody.count.should == original_count + 3
+ end
+end
+
+
+
diff --git a/spec/models/raw_email_spec.rb b/spec/models/raw_email_spec.rb
index 65780baed..889bb0783 100644
--- a/spec/models/raw_email_spec.rb
+++ b/spec/models/raw_email_spec.rb
@@ -12,14 +12,21 @@ describe User, "manipulating a raw email" do
@raw_email.data.should == "Hello, world!"
end
- it 'putting data in comes back out even if it has a backslash in it' do
- @raw_email.data = "This \\ that"
- @raw_email.save!
- @raw_email.reload
- STDERR.puts @raw_email.data
- STDERR.puts "This \\ that"
- @raw_email.data.should == "This \\ that"
- end
+ # XXX this test fails, hopefully will be fixed in later Rails.
+ # Doesn't matter too much for us for storing raw_emails, it would seem,
+ # but keep an eye out.
+
+ # This is testing a bug in Rails PostgreSQL code
+ # http://blog.aradine.com/2009/09/rubys-marshal-and-activerecord-and.html
+ # https://rails.lighthouseapp.com/projects/8994/tickets/1063-binary-data-broken-with-postgresql-adapter
+# it 'putting data in comes back out even if it has a backslash in it' do
+# @raw_email.data = "This \\ that"
+# @raw_email.save!
+# @raw_email.reload
+# STDERR.puts @raw_email.data
+# STDERR.puts "This \\ that"
+# @raw_email.data.should == "This \\ that"
+# end
end
diff --git a/spec/models/request_mailer_spec.rb b/spec/models/request_mailer_spec.rb
index 541f1da87..c77920905 100644
--- a/spec/models/request_mailer_spec.rb
+++ b/spec/models/request_mailer_spec.rb
@@ -115,7 +115,7 @@ describe RequestMailer, " when receiving incoming mail" do
deliveries.clear
end
- it "should dump messages to a request if marked to do so" do
+ it "should destroy the messages sent to a request if marked to do so" do
ActionMailer::Base.deliveries.clear
# mark request as anti-spam
ir = info_requests(:fancy_dog_request)
@@ -239,7 +239,7 @@ describe RequestMailer, 'when sending mail when someone has updated an old uncla
:law_used_full => 'Freedom of Information',
:title => 'Test request',
:public_body => @public_body,
- :display_status => 'Rejected.',
+ :display_status => 'Refused.',
:url_title => 'test_request')
@mail = RequestMailer.create_old_unclassified_updated(@info_request)
end
@@ -249,7 +249,7 @@ describe RequestMailer, 'when sending mail when someone has updated an old uncla
end
it 'should tell them what status was picked' do
- @mail.body.should match(/"rejected."/)
+ @mail.body.should match(/"refused."/)
end
it 'should contain the request path' do
diff --git a/spec/models/track_mailer_spec.rb b/spec/models/track_mailer_spec.rb
index 02f3bc991..44619e2bb 100644
--- a/spec/models/track_mailer_spec.rb
+++ b/spec/models/track_mailer_spec.rb
@@ -48,9 +48,11 @@ describe TrackMailer do
describe 'for each tracked thing' do
before do
+ @track_things_sent_emails_array = []
+ @track_things_sent_emails_array.stub!(:find).and_return([]) # this is for the date range find (created in last 14 days)
@track_thing = mock_model(TrackThing, :track_query => 'test query',
- :track_things_sent_emails => [],
- :created_at => Time.utc(2007, 4, 12, 23, 59))
+ :track_things_sent_emails => @track_things_sent_emails_array,
+ :created_at => Time.utc(2007, 11, 9, 23, 59))
TrackThing.stub!(:find).and_return([@track_thing])
@xapian_search = mock('xapian search', :results => [])
@found_event = mock_model(InfoRequestEvent, :described_at => @track_thing.created_at + 1.day)
@@ -59,13 +61,13 @@ describe TrackMailer do
end
it 'should ask for the events returned by the tracking query' do
- InfoRequest.should_receive(:full_search).with([InfoRequestEvent], 'test query', 'described_at', true, nil, 200, 1).and_return(@xapian_search)
+ InfoRequest.should_receive(:full_search).with([InfoRequestEvent], 'test query', 'described_at', true, nil, 100, 1).and_return(@xapian_search)
TrackMailer.alert_tracks
end
it 'should not include in the email any events that the user has already been sent a tracking email about' do
sent_email = mock_model(TrackThingsSentEmail, :info_request_event_id => @found_event.id)
- @track_thing.stub!(:track_things_sent_emails).and_return([sent_email])
+ @track_things_sent_emails_array.stub!(:find).and_return([sent_email]) # this is for the date range find (created in last 14 days)
@xapian_search.stub!(:results).and_return([@search_result])
TrackMailer.should_not_receive(:deliver_event_digest)
TrackMailer.alert_tracks
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index f4df22e9d..5d2bd39c7 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -231,8 +231,8 @@ describe User, " when making name and email address" do
end
end
-
-describe User, " when setting a profile photo" do
+# XXX not finished
+describe User, "when setting a profile photo" do
before do
@user = User.new
@user.name = "Sensible User"
diff --git a/spec/models/xapian_spec.rb b/spec/models/xapian_spec.rb
index b0c122f50..c2a87b969 100644
--- a/spec/models/xapian_spec.rb
+++ b/spec/models/xapian_spec.rb
@@ -3,42 +3,52 @@ require File.dirname(__FILE__) + '/../spec_helper'
describe User, " when indexing users with Xapian" do
fixtures :users
- before(:all) do
- rebuild_xapian_index
- end
-
it "should search by name" do
+ rebuild_xapian_index
# def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page)
xapian_object = InfoRequest.full_search([User], "Silly", 'created_at', true, nil, 100, 1)
xapian_object.results.size.should == 1
xapian_object.results[0][:model].should == users(:silly_name_user)
end
-
end
describe PublicBody, " when indexing public bodies with Xapian" do
- fixtures :public_bodies
+ fixtures :public_bodies, :incoming_messages, :outgoing_messages, :raw_emails, :comments
- before(:all) do
+ it "should search index the main name field" do
rebuild_xapian_index
- end
- it "should search index the main name field" do
xapian_object = InfoRequest.full_search([PublicBody], "humpadinking", 'created_at', true, nil, 100, 1)
xapian_object.results.size.should == 1
xapian_object.results[0][:model].should == public_bodies(:humpadink_public_body)
end
it "should search index the notes field" do
+ rebuild_xapian_index
+
xapian_object = InfoRequest.full_search([PublicBody], "albatross", 'created_at', true, nil, 100, 1)
xapian_object.results.size.should == 1
xapian_object.results[0][:model].should == public_bodies(:humpadink_public_body)
end
+ it "should delete public bodies from the index when they are deleted" do
+ rebuild_xapian_index
+
+ xapian_object = InfoRequest.full_search([PublicBody], "albatross", 'created_at', true, nil, 100, 1)
+ xapian_object.results.size.should == 1
+ xapian_object.results[0][:model].should == public_bodies(:humpadink_public_body)
+
+ public_bodies(:humpadink_public_body).delete
+
+ update_xapian_index
+ xapian_object = InfoRequest.full_search([PublicBody], "albatross", 'created_at', true, nil, 100, 1)
+ xapian_object.results.size.should == 0
+ end
+
end
describe PublicBody, " when indexing requests by body they are to" do
- fixtures :public_bodies, :info_request_events, :info_requests
+ fixtures :public_bodies, :info_request_events, :info_requests, :raw_emails, :comments
it "should find requests to the body" do
rebuild_xapian_index
@@ -47,8 +57,6 @@ describe PublicBody, " when indexing requests by body they are to" do
end
it "should update index correctly when URL name of body changes" do
- verbose = false
-
# initial search
rebuild_xapian_index
xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:tgq", 'created_at', true, nil, 100, 1)
@@ -60,7 +68,7 @@ describe PublicBody, " when indexing requests by body they are to" do
body.short_name = 'GQ'
body.save!
body.url_name.should == 'gq'
- ActsAsXapian.update_index(true, verbose) # true = flush to disk
+ update_xapian_index
# check we get results expected
xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:tgq", 'created_at', true, nil, 100, 1)
@@ -71,10 +79,32 @@ describe PublicBody, " when indexing requests by body they are to" do
models_found_before.should == models_found_after
end
+
+ # if you index via the Xapian TermGenerator, it ignores terms of this length,
+ # this checks we're using Document:::add_term() instead
+ it "should work with URL names that are longer than 64 characters" do
+ rebuild_xapian_index
+
+ # change the URL name of the body
+ body = public_bodies(:geraldine_public_body)
+ body.short_name = 'The Uncensored, Complete Name of the Quasi-Autonomous Public Body Also Known As Geraldine'
+ body.save!
+ body.url_name.size.should > 70
+ update_xapian_index
+
+ # check we get results expected
+ xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:tgq", 'created_at', true, nil, 100, 1)
+ xapian_object.results.size.should == 0
+ xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:gq", 'created_at', true, nil, 100, 1)
+ xapian_object.results.size.should == 0
+ xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_from:" + body.url_name, 'created_at', true, nil, 100, 1)
+ xapian_object.results.size.should == 4
+ models_found_after = xapian_object.results.map { |x| x[:model] }
+ end
end
describe User, " when indexing requests by user they are from" do
- fixtures :users, :info_request_events, :info_requests
+ fixtures :users, :info_request_events, :info_requests, :incoming_messages, :outgoing_messages, :raw_emails, :comments
it "should find requests from the user" do
rebuild_xapian_index
@@ -82,9 +112,57 @@ describe User, " when indexing requests by user they are from" do
xapian_object.results.size.should == 4
end
- it "should update index correctly when URL name of user changes" do
- verbose = false
+ it "should find just the sent message events from a particular user" do
+ rebuild_xapian_index
+ # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page)
+ xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith variety:sent", 'created_at', true, nil, 100, 1)
+ xapian_object.results.size.should == 2
+ xapian_object.results[1][:model].should == info_request_events(:useless_outgoing_message_event)
+ xapian_object.results[0][:model].should == info_request_events(:silly_outgoing_message_event)
+ end
+
+ it "should not find it when one of the request's users is changed" do
+ rebuild_xapian_index
+ silly_user = users(:silly_name_user)
+ naughty_chicken_request = info_requests(:naughty_chicken_request)
+ naughty_chicken_request.user = silly_user
+ naughty_chicken_request.save!
+ update_xapian_index
+
+ # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page)
+ xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, 'request_collapse', 100, 1)
+ xapian_object.results.size.should == 1
+ xapian_object.results[0][:model].should == info_request_events(:silly_comment_event)
+ end
+
+ it "should not get confused searching for requests when one user has a name which has same stem as another" do
+ rebuild_xapian_index
+
+ bob_smith_user = users(:bob_smith_user)
+ bob_smith_user.name = "John King"
+ bob_smith_user.url_name.should == 'john_king'
+ bob_smith_user.save!
+
+ silly_user = users(:silly_name_user)
+ silly_user.name = "John K"
+ silly_user.url_name.should == 'john_k'
+ silly_user.save!
+
+ naughty_chicken_request = info_requests(:naughty_chicken_request)
+ naughty_chicken_request.user = silly_user
+ naughty_chicken_request.save!
+
+ update_xapian_index
+
+ # def InfoRequest.full_search(models, query, order, ascending, collapse, per_page, page)
+ xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:john_k", 'created_at', true, 'request_collapse', 100, 1)
+ xapian_object.results.size.should == 1
+ xapian_object.results[0][:model].should == info_request_events(:silly_outgoing_message_event)
+ end
+
+
+ it "should update index correctly when URL name of user changes" do
# initial search
rebuild_xapian_index
xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, nil, 100, 1)
@@ -96,7 +174,7 @@ describe User, " when indexing requests by user they are from" do
u.name = 'Robert Smith'
u.save!
u.url_name.should == 'robert_smith'
- ActsAsXapian.update_index(flush_to_disk=true, verbose)
+ update_xapian_index
# check we get results expected
xapian_object = InfoRequest.full_search([InfoRequestEvent], "requested_by:bob_smith", 'created_at', true, nil, 100, 1)
@@ -110,7 +188,7 @@ describe User, " when indexing requests by user they are from" do
end
describe User, " when indexing comments by user they are by" do
- fixtures :users, :info_request_events, :info_requests, :comments
+ fixtures :users, :info_request_events, :info_requests, :comments, :incoming_messages, :outgoing_messages, :raw_emails, :comments
it "should find requests from the user" do
rebuild_xapian_index
@@ -119,8 +197,6 @@ describe User, " when indexing comments by user they are by" do
end
it "should update index correctly when URL name of user changes" do
- verbose = false
-
# initial search
rebuild_xapian_index
xapian_object = InfoRequest.full_search([InfoRequestEvent], "commented_by:silly_emnameem", 'created_at', true, nil, 100, 1)
@@ -132,7 +208,7 @@ describe User, " when indexing comments by user they are by" do
u.name = 'Silly Name'
u.save!
u.url_name.should == 'silly_name'
- ActsAsXapian.update_index(true, verbose) # true = flush to disk
+ update_xapian_index
# check we get results expected
xapian_object = InfoRequest.full_search([InfoRequestEvent], "commented_by:silly_emnameem", 'created_at', true, nil, 100, 1)
@@ -146,7 +222,7 @@ describe User, " when indexing comments by user they are by" do
end
describe InfoRequest, " when indexing requests by their title" do
- fixtures :info_request_events, :info_requests, :incoming_messages
+ fixtures :info_request_events, :info_requests, :incoming_messages, :raw_emails, :comments
it "should find events for the request" do
rebuild_xapian_index
@@ -156,15 +232,13 @@ describe InfoRequest, " when indexing requests by their title" do
end
it "should update index correctly when URL title of request changes" do
- verbose = false
-
# change the URL name of the body
rebuild_xapian_index
ir = info_requests(:naughty_chicken_request)
ir.title = 'Really naughty'
ir.save!
ir.url_title.should == 'really_naughty'
- ActsAsXapian.update_index(true, verbose) # true = flush to disk
+ update_xapian_index
# check we get results expected
xapian_object = InfoRequest.full_search([InfoRequestEvent], "request:how_much_public_money_is_wasted_o", 'created_at', true, nil, 100, 1)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 005ae8174..ddb9ab14b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -7,7 +7,7 @@ require 'spec/rails'
Spec::Runner.configure do |config|
config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
- config.fixture_path = RAILS_ROOT + '/spec/fixtures'
+ config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
# You can declare fixtures for each behaviour like this:
# describe "...." do
@@ -38,10 +38,18 @@ def load_file_fixture(file_name)
end
def rebuild_xapian_index
+ # XXX could for speed call ActsAsXapian.rebuild_index directly, but would
+ # need model name list, and would need to fix acts_as_xapian so can call writes
+ # and reads mixed up (it asserts where it thinks it can't do this)
rebuild_name = File.dirname(__FILE__) + '/../script/rebuild-xapian-index'
Kernel.system(rebuild_name) or raise "failed to launch #{rebuild_name}, error bitcode #{$?}, exit status: #{$?.exitstatus}"
end
+def update_xapian_index
+ verbose = false
+ ActsAsXapian.update_index(flush_to_disk=true, verbose)
+end
+
# Validate an entire HTML page
def validate_html(html)
$tempfilecount = $tempfilecount + 1
diff --git a/spec/views/public_body/show.rhtml_spec.rb b/spec/views/public_body/show.rhtml_spec.rb
index fbd05b9d1..5fec84af4 100644
--- a/spec/views/public_body/show.rhtml_spec.rb
+++ b/spec/views/public_body/show.rhtml_spec.rb
@@ -47,7 +47,7 @@ describe "when viewing a body" do
it "should tell total number of requests" do
render "public_body/show"
- response.should have_tag("h2", "4 Freedom of Information requests made")
+ response.should include_text("4 Freedom of Information requests made")
end
it "should cope with no results" do
@@ -70,6 +70,15 @@ describe "when viewing a body" do
end
end
+ it "should link to Scottish Charity Regulator site if we have an SC number" do
+ @pb.stub!(:charity_number).and_return('SC1234')
+ render "public_body/show"
+ response.should have_tag("div#request_sidebar") do
+ with_tag("a[href*=?]", /www.oscr.org.uk.*id=SC1234$/)
+ end
+ end
+
+
it "should not link to Charity Commission site if we don't have number" do
render "public_body/show"
response.should have_tag("div#request_sidebar") do
diff --git a/spec/views/request/show.rhtml_spec.rb b/spec/views/request/show.rhtml_spec.rb
index b17521a4b..22860f0c5 100644
--- a/spec/views/request/show.rhtml_spec.rb
+++ b/spec/views/request/show.rhtml_spec.rb
@@ -4,7 +4,8 @@ describe 'when viewing an information request' do
before do
@mock_body = mock_model(PublicBody, :name => 'test body',
- :url_name => 'test_body')
+ :url_name => 'test_body',
+ :is_school? => false)
@mock_user = mock_model(User, :name => 'test user',
:url_name => 'test_user')
@mock_request = mock_model(InfoRequest, :title => 'test request',
@@ -17,22 +18,22 @@ describe 'when viewing an information request' do
:prominence => 'normal')
end
- def do_render
+ def request_page
assigns[:info_request] = @mock_request
assigns[:info_request_events] = []
assigns[:status] = @mock_request.calculate_status
- template.stub!(:render)
+ template.stub!(:render_partial)
render 'request/show'
end
it 'should show the sidebar' do
- template.should_receive(:render).with(:partial => 'sidebar')
- do_render
+ template.should_receive(:render_partial).with(:partial => 'sidebar', :locals => {})
+ request_page
end
it 'should show the actions people can take' do
- template.should_receive(:render).with(:partial => 'after_actions')
- do_render
+ template.should_receive(:render_partial).with(:partial => 'after_actions', :locals => {})
+ request_page
end
describe 'when a status update has been requested' do
@@ -42,7 +43,7 @@ describe 'when viewing an information request' do
end
it 'should show the first form for describing the state of the request' do
- do_render
+ request_page
response.should have_tag("div.describe_state_form#describe_state_form_1")
end
@@ -55,12 +56,12 @@ describe 'when viewing an information request' do
end
it 'should show the first form for describing the state of the request' do
- do_render
+ request_page
response.should have_tag("div.describe_state_form#describe_state_form_1")
end
it 'should show the second form for describing the state of the request' do
- do_render
+ request_page
response.should have_tag("div.describe_state_form#describe_state_form_2")
end
@@ -86,7 +87,7 @@ describe 'when viewing an information request' do
end
it 'should show a link to follow up the last response with clarification' do
- do_render
+ request_page
expected_url = "http://test.host/request/#{@mock_request.id}/response/#{@mock_response.id}#followup"
response.should have_tag("a[href=#{expected_url}]", :text => 'send a follow up message')
end
@@ -100,7 +101,7 @@ describe 'when viewing an information request' do
end
it 'should show a link to follow up the request without reference to a specific response' do
- do_render
+ request_page
expected_url = "http://test.host/request/#{@mock_request.id}/response#followup"
response.should have_tag("a[href=#{expected_url}]", :text => 'send a follow up message')
end
diff --git a/todo.txt b/todo.txt
index bfc265e33..cbcf2efd4 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,29 +1,53 @@
-Segmentation fault:
-IncomingMessage.find(24705).info_equest_events[0].search_text_main(true)
+Next (things that will reduce admin time mainly)
+====
-s = IncomingMessage.find(2); text = s.get_main_body_text_internal; s.cached_main_body_text = text; s.save!
+Merge with New Zealand code base properly
+Handle bounce messages from alerts automatically
-overdue
-date_response_required_by
-waiting_response_overdue => waiting_response_slow, waiting_response_overdue
+Make outgoing requests and follow ups get CCed to our backup mailbox, so that
+can do data recovery more easily
+Make it so when you make followups the whole request is shown on the page.
+Remove all show_response URLs, and replace with a special version of the
+request URL with a new input box at the bottom and a hash link to it?
+<< when following links such as "I'm about to send clarification", a
+form appears into which the reply can be typed. However, the
+previous correspondence in that thread is not shown.
+I usually open a new tab to see what was written previously before
+writing in the form. It might be useful if the previous
+correspondence were instead shown on the page in which the form
+appears. >>
-Move comments to use new system first
-Show message to signed in user that others can't see this part
-Make sure hidden things don't show in search snippets
- in models/comment.rb: # So when made invisble it vanishes
-Remove comments visible
+Word doc conversion never works for Lisa, maybe use more software to try
+to convert her docs.
+Finish profile photo - maybe use Gravatar, but opt in for existing users?
-Finish profile photo
+Ask people for annotation immediately after they have submitted their request
+Ask for annotation about what they learnt from request?
+Admin button to resend request one off to particular address
-test if get_attachments_for_display called multiple times in one request?
+Private request premium feature
+http://www.activemerchant.org/
+
+Froze during reindex, is the doc files inside the .zip here:
+ http://www.whatdotheyknow.com/request/last_collection_times#incoming-8405
+ ActsAsXapian.rebuild_index InfoRequestEvent 16061
+ foi 23175 0.0 0.0 5176 1472 pts/1 S+ 14:16 0:00 /bin/sh /usr/bin/wvText /tmp/foiextract20100619-20578-1gcbuqz-0 /tmp/foiextract20100619-20578-1gcbuqz-0.txt
+ foi 23180 0.0 0.0 4664 1220 pts/1 S+ 14:16 0:00 /bin/sh /usr/bin/wvHtml -1 /tmp/foiextract20100619-20578-1gcbuqz-0 --targetdir=/tmp wv-XeJwGT
+Also freezes Abiword, but not catdoc
-sending an email alert for this query takes crazy long
- query = 'variety:response (status:successful OR status:partially_successful)'
-something to do with the way event_digest loops with lots of responses?
+
+Performance
+===========
+
+Enable Directory Indexing, and disable atime (@glynwintle suggests)
+http://www.softpanorama.org/Internals/Filesystems/linux_ext2_ext3.shtml
+(as/if we have caches with lots of files in a direcory)
+
+test if get_attachments_for_display called multiple times in one request?
Some requests to lower memory use of still:
PID: 676 CONSUME MEMORY: 16968 KB Now: 102604 KB http://www.whatdotheyknow.com/request/parking_ticket_data_81
@@ -31,35 +55,46 @@ PID: 2036 CONSUME MEMORY: 129368 KB Now: 179652 KB http://www.whatd
- search engines shouldn't be going for those URLs. and do they really need to
unpack so much? could use snippet cache.
+Page cacheing - the cookie setting stops the expires_on caching working well,
+as even when not logged in second page you go to isnt serverd from Squid cache.
+Things to make bots not crawl somehow:
+ /request/13683/response?internal_review=1
+ /request/febrile_neutropenia_154?unfold=1
-Next (things that will reduce admin time mainly)
-====
+cached_main_body_text could store the privacy censored versions now, since
+cached_attachment_text_clipped does (and clears it when censor rules are edited)
-Ask people for annotation immediately after they have submitted their request
-Ask for annotation about what they learnt from request?
+Renaming of a body, or changing its domain, should clear the cached bubbles of
+all requests to that body.
+
+
+Letting you hide individual events (incoming/outgoing messages, annotations)
+==================================
+
+*** this needs either removing or finishing, it is half done. Has the
+database entries but doesn't use them yet.
-Have proper house rules / site terms and conditions page.
-Perhaps borrow from these house rules:
- http://www.theyworkforyou.com/houserules/
-
-Lots of authorities are starting to complain about how their record looks on
-their page - e.g responses not classified by users. That users may classify
-incorrectly. Change wording to make it clear statuses are users opinion ?.
-* Meanwhile mention in help that authorities can do this
-* And ask that they include URL of requests in emails when talking about them
-
-Finish up the request game, and market it a bit more.
- * Or you can do it yourself (if you are not logged in and request isn't classified)
- * Market it from every request that isn't categorised
- (and when you categorise one)
-Check up on how the public old request status editing is being used in
-practice. Think about UI a bit more to try and up rate, and UI of alerts to
-requester.
-Offer user_withdrawn as option in many GUI?
-
-Put the request from address in the database,
- XXX not so sure about that, as need fuzzy rule for matching/guessing according to type
+Move comments to use new system first
+Show message to signed in user that others can't see this part
+Make sure hidden things don't show in search snippets
+ in models/comment.rb: # So when made invisble it vanishes
+Remove comments visible
+
+
+Later
+=====
+
+check-recent-requests-sent probably doesn't work, as exim log lines wouldn't
+be load in case where the envelope from gets broken?
+
+Point all MX records to one server, so can see incoming messages in exim logs also.
+Hmmm, but less robust. Run the exim log grabber across all mail servers?
+
+XXX Not sure all this matters really, requests seems to be getting through better these days?
+Make request addresses easier to type in again, and routing work better:
+* Put the request from address in the database,
+ XXX make sure it knows the type, as need fuzzy rule for matching/guessing according to type
then change the rule for making it.
* Change holding pen to lookup hash e.g. 1bd8ea of the request address in database
(so gives good guess it the hash is right, but the number is wrong)
@@ -69,7 +104,6 @@ julian.todd@section44.whatdotheyknow.com
* Use words from a dictionary, e.g. cat, mouse, rat, hat etc.
* Use single words from the request, e.g. section, terrorism, allotment
* Make sure avoid FROM_ENDS_IN_NUMS rule in Spam Assassin
-* Identify authority by who it is from.
* It looks like an error generated by GFI MailEssentials, see p62 of chapter
11 of the manual at http://www.gfi.com/mes/me11manual.pdf which states:
7. Check if emails contain more than X numbers in the MIME from:
@@ -78,27 +112,31 @@ julian.todd@section44.whatdotheyknow.com
automatically create reply-to: addresses on hotmail and other free email
services. Frequently they use 3 or more numbers in the name to make sure
the reply-to: is unique.
-* People seem to have trouble typing in the request address again
* Use FOI code allocated by authority to work out where emails are to go
* Second request to same authority by same person - tell them to be sure
to use the right email
* Improve routing from Exim so copes with addresses not having request- prefix.
-check-recent-requests-sent probably doesn't work, as exim log lines wouldn't
-be load in case where the envelope from gets broken?
+When on a small screen, the actual form when making a new request is below
+the fold, and it isn't obvious what you need to do. (Seen while watching
+a new user try to make a request)
-Admin button to resend request one off to particular address
+Put public body name in search text for each info request, so that people
+typing in a word and a body name in the search (have seen people new
+to the site doing that) do find things
-Add explicit option for user to select "misdelivered to the wrong request"
-and let people move them to the right place.
-(Julian wants that too)
+Completely remove the "feed" option from tracks
+Show the Subject: lines on request pages. Perhaps only show them where they are
+substantively different (modulo Re: and Fwd:) from the title and other subjects
+- so you can see any FOI code number the authority has put in the subject.
-Later
-=====
+For Scotland, don't need to say "normally" equivocally when it is taking more than 20 days
+(as there is no public interest test).
-Give authorities interface for editing their request email address
-and resend messages to them
+Add explicit option for user to select "misdelivered to the wrong request"
+and let people move them to the right place.
+(Julian wants that too)
Perhaps fold up request pages more by default - don't show known acknowledgements until
you click and some (javascript) expands them.
@@ -109,30 +147,6 @@ Esp. when filling in a form on the same page.
Somehow make clear that a "rejection because referring to info already
in public domain" should really be marked sucessful.
-Install more recent poppler-utils
- e.g. 0.12.0 can definitely convert this to HTML, extacting the images:
- http://www.whatdotheyknow.com/request/13903/response/36117/attach/html/4/FOI%20beaver%20site%20species%20audit%20SNH%20review%20of%20proposal%20redact.pdf.html
-Really need a "pdftk -nodrm" to remove compression from encrypted PDFs, so strips emails from e.g.:
- http://www.whatdotheyknow.com/request/14414/response/38590/attach/html/3/090807%20FOI.pdf.html
- ... this misses a whole page out (someone emailed us)
- http://www.whatdotheyknow.com/request/unredacted_expense_claims_for_jo#incoming-49674
-
-cached_main_body_text could store the privacy censored versions now, since
-cached_attachment_text_clipped does (and clears it when censor rules are edited)
-
-Things to make bots not crawl somehow:
- /request/13683/response?internal_review=1
- /request/febrile_neutropenia_154?unfold=1
-
-Maybe don't email you in alert subscriptions about annotations you made on
-other people's requests?
-
-Renaming of a body, or changing its domain, should clear the cached bubbles of
-all requests to that body.
-
-Page cacheing - the cookie setting stops the expires_on caching working well,
-as even when not logged in second page you go to isnt serverd from Squid cache.
-
Emails sent to stopped requests should follow RFC: http://tools.ietf.org/html/rfc3834
Shouldn't bounce message back to Auto-Submitted
Should check from address being replied to is valid
@@ -150,39 +164,14 @@ For followups, have radio button to say is it a new request or followup
Do by uncommenting the "new information" option when writing a followup, so
that it makes a new request
-Point all MX records to one server, so can see incoming messages in exim logs also.
-Hmmm, but less robust. Run the exim log grabber across all mail servers?
-
-Links to "a response" from timeline aren't to right page any more.
-
-Change it to store emails as files in the filesystem? For speed.
-Should have simpler system for us to upload files sent to us via CD etc.
-Currently we have to manually put them in the files directory on the vhost.
-Make it so web upload interface copes gracefully with arbitarily large messages
-(it causes speed trouble having them in the database right now)
-
-Remove all show_response URLs, and replace with a special version of the
-request URL with a new input box at the bottom and a hash link to it?
-<< when following links such as "I'm about to send clarification", a
-form appears into which the reply can be typed. However, the
-previous correspondence in that thread is not shown.
-I usually open a new tab to see what was written previously before
-writing in the form. It might be useful if the previous
-correspondence were instead shown on the page in which the form
-appears. >>
-
When it prompts error_message people to send annotation, maybe just show them
the email address of the error to check then and there?
-Should really make replies munge subject of last response, rather than start
-afresh with subject - authorities use FOI code in subject as here:
-http://www.whatdotheyknow.com/request/causes_of_the_financial_crisis#incoming-12779
-
If you've already conducted an internal review, at all places
- when on unhappy/url
- when on not held link
- on the page for the request
-don't offer it again.
+don't offer it again, as they've already done it.
Example of completed review:
http://www.whatdotheyknow.com/request/request_for_full_disclosure_of_b#incoming-9267
@@ -205,19 +194,10 @@ Sort requests on user page by status.
in order of 'last action'... to quickly see what was most overdue."
Group list on user page by authority
-Make search know about uncategorised requests and timed out requests.
-And make search able to do *current* status in general as operator.
-
I have several email alerts set up. Is there any chance they could include part
(or, preferably, all) of the search criterion in the Subject: line? :o)
(Perhaps do it in the case when only one search criterion makes the mail)
-Test data dumper that removes sensitive data, but lets trusted people play with
-whole database on their own machine without risk of compromise (for Tony)
-- can avoid rebuilding emails, attachments etc. sanitized provided we don't
-mind leaking out email address ot requests etc. to the trusted person (in contrast
-can easily totally remove private emails in the user table)
-
Search for text "internal review" in followups and add warning if they aren't
using the internal review mode.
@@ -235,7 +215,6 @@ CSS / design things
but some magic Ruby thing is printing the error span.
- Improve CSS on IE7 for large images in docs
http://www.whatdotheyknow.com/request/3289/response/7810/attach/html/3/20081023ReplyLetter.pdf.html
- - favicon.ico would be nice
- Get Atom feed of search results to include stylesheet for highlighting words in
yellow somehow
@@ -243,17 +222,12 @@ When doing search, people often just want it to show the whole page. Perhaps
all listing should just link to top of page, rather than # links for outgoing
incoming, or perhaps just some of them.
-Help page improvements:
- Needs to say somewhere in flow that you can make a request privately (I think quite a
- few people get to us via Google and don't realise that they can)
- Add "Who should I make my request to?" - make flow better after first section, to abrupt now
- Some more traditional help such as:
- * Information about how to track requests and RSS feeds
- * Information about how to contacting other users
+Some more traditional help (in a new section in the help) such as:
+ * Information about how to track requests and RSS feeds
+ * Information about how to contacting other users
Show similar requests after you have filed yours - maybe on preview too.
-Test code for FOI officer upload
Test code for rendering lots of different attachments and filetypes
Test code for internal review submitting
@@ -265,33 +239,47 @@ Protect from CSRF with this in app controller (care it doesn't break anything):
Look at quote_address_if_necessary in actionmailer's quoting.rb - why did it
not work for the email address with "@" in its name part?
-From an email, isn't stripping spaces right.
- "Met Office sent a response to Andrew Montford (14 August 2008)"
-Also should group by the request id for search queries (so all appear
+Should group by the request id for search queries (so all appear
together when request and response mention same term)
Something to check which tags are used but aren't in PublicBody category lists
+Change it to store emails as files in the filesystem? For speed of backup if nothing else.
+Should have simpler system for us to upload files sent to us via CD etc.
+Currently we have to manually put them in the files directory on the vhost.
+Make it so web upload interface copes gracefully with arbitarily large messages
+(it causes speed trouble having them in the database right now)
+
Compress the emails in the database
-Don't store the cached text in backups
+Don't store the cached text in backups - maybe keep it in its own table to avoid that?
-Edits to outgoing/incoming/title won't be reindexed in Xapian (maybe just reindex all once a week)
- And maybe edits to prominence, which is more upsetting
- Never updates cached attachment text unless cache is explicitly cleared (which
- might matter with software updates, or code changes)
-This does it all:
-$ ./script/clear-incoming-text-cache ; ./script/rebuild-xapian-index
-(clear-incoming-text-cache needs renaming to make it clearer it does clear the disk cache too,
-and that code testing)
+Other references to title field changes don't get search index updated when title is altered
+ (e.g. when a public body is renamed)
+Maybe just reindex all once a week, but it is a bit slow now, so perhaps do it properly.
+$ ./script/rebuild-xapian-index
Renaming public authorities will break alerts on them. For basic alerts the
structured info is there so this should just be fixed. For searches, perhaps
Xapian index should search url_name history as well?
+We have a policy of not renaming them in some cases, which helps a bit.
+
+Never updates cached attachment text unless cache is explicitly cleared (which
+ might matter with software updates, or code changes). Should we clear the
+ cache automatically every month in the middle of the night or something?
+$ ./script/clear-caches
Display and indexing of response emails/attachments
---------------------------------------------------
+Install more recent poppler-utils
+ e.g. 0.12.0 can definitely convert this to HTML, extacting the images:
+ http://www.whatdotheyknow.com/request/13903/response/36117/attach/html/4/FOI%20beaver%20site%20species%20audit%20SNH%20review%20of%20proposal%20redact.pdf.html
+Really need a "pdftk -nodrm" to remove compression from encrypted PDFs, so strips emails from e.g.:
+ http://www.whatdotheyknow.com/request/14414/response/38590/attach/html/3/090807%20FOI.pdf.html
+ ... this misses a whole page out (someone emailed us)
+ http://www.whatdotheyknow.com/request/unredacted_expense_claims_for_jo#incoming-49674
+
Failed to detect attachments are emails and decode them:
http://www.whatdotheyknow.com/request/malicious_communication_act#incoming-12964
@@ -304,28 +292,19 @@ http://www.whatdotheyknow.com/request/19976/response/51468/attach/3/TU%20Members
Search for "OIC" for some more examples
Worth doing View as HTML for .docx probably
+View as HTML for .txt requested
VSD files vsdump - example in zip file
http://www.whatdotheyknow.com/request/dog_control_orders#incoming-3510
doing file RESPONSE/Internal documents/Briefing with Contact Islington/Contact Islington Flowchart Jul 08.vsd content type
-Use Ruby msg
- http://code.google.com/p/ruby-msg/
-To decode Outlook .msg (.oft?) files, e.g.
- http://www.whatdotheyknow.com/request/immediate_response_team_deployme
- http://www.whatdotheyknow.com/request/chinese_names_for_british_politi
Search for other file extensions that we have now and look for ones we could
and should be indexing
(call IncomingMessage.find_all_unknown_mime_types to find them - needs
updating to do it in clumps as all requests won't load in RAM now )
-Make tables prettier in view as HTML, just normal thick borders.
-http://www.whatdotheyknow.com/request/1610/response/8093/attach/html/3/2008.10.29%20Reply.doc.html
-
Render HTML alternative rather than text (so tables look good) e.g.:
http://www.whatdotheyknow.com/request/parking_policy
-And indeed so links work:
- http://www.whatdotheyknow.com/request/recycling_levels_in_the_winchest
Make HTML attachments have view as HTML :)
http://www.whatdotheyknow.com/request/enforced_medication#incoming-7395
@@ -343,6 +322,9 @@ Orientation wrong:
Bug in wvHtml, segfaults when converting this:
http://www.whatdotheyknow.com/request/subject_access_request_guide_sar#incoming-10242
+Images aren't coming out here
+ http://www.whatdotheyknow.com/request/33682/response/83455/attach/html/3/100428%20Reply%201519%2010.doc.html
+
Doesn't detect doc type of a few garbage results in this list right:
http://www.whatdotheyknow.com/search/UWE
@@ -407,6 +389,9 @@ Quoting fixing TODO:
http://www.whatdotheyknow.com/request/independent_psychological_assess#incoming-52956 # shows text as attachment when could be inline due to multipart nature?
+ http://www.whatdotheyknow.com/request/bnp_membership_list_43#incoming-59204 # not detecting original message
+ http://www.whatdotheyknow.com/request/maximum_pedestrian_crossing_wait#incoming-34094 # not detecting original message
+
Display pasted quotes in annotations better:
http://www.whatdotheyknow.com/request/scientology_incidents#comment-3352
@@ -415,6 +400,28 @@ Display pasted quotes in annotations better:
Totally new features
--------------------
+Tag your own requests so you can keep track of them more easily (Lisa asked for
+this - is definitely only whole requests needed)
+
+Scribd embedded version of documents
+
+Read reply - ask for exchange read receipts, and show if mail was read.
+
+Telephone numbers. Add advice in workflow to call authority first to check
+form they have info in. Store telephone numbers in database.
+
+Give authorities interface for editing their request email address
+and resend messages to them
+
+Make search know about uncategorised requests and timed out requests.
+And make search able to do *current* status in general as operator.
+
+Test data dumper that removes sensitive data, but lets trusted people play with
+whole database on their own machine without risk of compromise (for Tony)
+- can avoid rebuilding emails, attachments etc. sanitized provided we don't
+mind leaking out email address ot requests etc. to the trusted person (in contrast
+can easily totally remove private emails in the user table)
+
Have an interface for users to be able to suggest new authorities and give
their email address (perhaps just have admins validate / approve it)
@@ -428,12 +435,16 @@ Add tips on using the law, e.g.:
their policy is, and hwo they are implementing it. Ask national things what
odcuments set local policies.
+Add note by any exemption to the page on FOI Wiki Add note on mention of
+"Re-Use of Public Sector Information Regulations 2005"
+to the appropriate FAQ.
+
Hyperlink Section 1(3) to the act
http://www.whatdotheyknow.com/request/university_investment_in_the_arm#incoming-86
and to guidance notes
http://www.ico.gov.uk/what_we_cover/freedom_of_information/guidance.aspx
-Jump to a random request :)
+Link to /random jump to a random request somewhere
Do conversion tracking on endpoints in WDTK, advertise perhaps TWFY, or perhaps
donations to mySociety.
@@ -450,19 +461,6 @@ Make text boxes autogrow as you type into them.
(10:32:52) Matthew: their function is called autogrow_textarea() by the way, if you just want to look at it...
thanks :) I won't do it now as there are more important things, I was just accidentally impressed
-Set arbitary alarms, to be alerted when a date set by authority for themselves
-is passed.
-
-Antispam on contact form (a recaptcha presumably, as we have them elsewhere)
-
-Find a way to get corrected email addresses from responses - somehow getting
-user to send them in? "For future FOI requests please email ..."
-e.g. as in http://www.whatdotheyknow.com/request/cost_of_council_website_17#incoming-1870
-we found largely by accident.
-
-Rate limit requests using Ratty, with a freeze thing WTT-style, to detect
-vexatious requests.
-
Editable user profile, including photo upload
.tif files are hard for people to view as multi page, consider automatically
@@ -470,17 +468,12 @@ separating out the pages as separate links (to .png files or whatever)
http://www.whatdotheyknow.com/request/windsor_maidenhead_council_commo#incoming-1910
Heck, may as well give thumbnails of all images, indeed all docs while you're at it :)
-In "view as HTML" let people highlight paragraphs, and link to them
-(like Julian's UN PDF highlighting thing)
-
Add geographical location of councils, PCTs etc.
Have a single button to sign up to alerts on authorities for your postcode
NHS postcode database:
http://www.ons.gov.uk/about-statistics/geography/products/geog-products-postcode/nhspd/index.html
-Make request preview have a URL so you can show it to someone else before
-sending it :)
-
+Make request preview have a URL so you can show it to someone else before sending it :)
Proposed request submission queue with comments - new requests don't get sent straight
away, but are delayed while people help improve them.
@@ -489,6 +482,7 @@ http://www.ico.gov.uk/Home/tools_and_resources/decision_notices.aspx
Description for each body as to what info it holds
Link to:
+ Company number
Aliases (not just short name, but multiple real names e.g. for museums)
Disclosure logs
Publication schemes (http://www.ico.gov.uk/what_we_cover/freedom_of_information/publication_schemes.aspx)
diff --git a/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb b/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb
index 941a59735..3aba29e7b 100644
--- a/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb
+++ b/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb
@@ -142,6 +142,7 @@ module ActsAsXapian
@@stopper = Xapian::SimpleStopper.new
@@stopper.add("and")
+ @@stopper.add("of")
@@stopper.add("&")
@@query_parser.stopper = @@stopper
@@ -166,7 +167,11 @@ module ActsAsXapian
raise "Z is reserved for stemming terms" if term[1] == "Z"
raise "Already have code '" + term[1] + "' in another model but with different prefix '" + @@terms_by_capital[term[1]] + "'" if @@terms_by_capital.include?(term[1]) && @@terms_by_capital[term[1]] != term[2]
@@terms_by_capital[term[1]] = term[2]
- @@query_parser.add_prefix(term[2], term[1])
+ # XXX use boolean here so doesn't stem our URL names in WhatDoTheyKnow
+ # If making acts_as_xapian generic, would really need to make the :terms have
+ # another option that lets people choose non-boolean for terms that need it
+ # (i.e. searching explicitly within a free text field)
+ @@query_parser.add_boolean_prefix(term[2], term[1])
end
end
if options[:values]
@@ -507,10 +512,10 @@ module ActsAsXapian
class ActsAsXapianJob < ActiveRecord::Base
end
- # Update index with any changes needed, call this offline. Only call it
+ # Update index with any changes needed, call this offline. Usually call it
# from a script that exits - otherwise Xapian's writable database won't
- # flush your changes. Specifying flush will reduce performance, but
- # make sure that each index update is definitely saved to disk before
+ # flush your changes. Specifying flush will reduce performance, but make
+ # sure that each index update is definitely saved to disk before
# logging in the database that it has been.
def ActsAsXapian.update_index(flush = false, verbose = false)
# STDOUT.puts("start of ActsAsXapian.update_index") if verbose
@@ -594,24 +599,43 @@ module ActsAsXapian
raise "found existing " + new_path + " which is not Xapian flint database, please delete for me" if not File.exist?(File.join(new_path, "iamflint"))
FileUtils.rm_r(new_path)
end
- ActsAsXapian.writable_init(".new")
# Index everything
batch_size = 1000
for model_class in model_classes
- model_class.transaction do
- 0.step(model_class.count, batch_size) do |i|
- STDOUT.puts("ActsAsXapian: New batch. From #{i} to #{i + batch_size}") if verbose
- models = model_class.find(:all, :limit => batch_size, :offset => i, :order => :id)
- for model in models
- STDOUT.puts("ActsAsXapian.rebuild_index #{model_class} #{model.id}") if verbose
- model.xapian_index
+ model_class_count = model_class.count
+ 0.step(model_class_count, batch_size) do |i|
+ # We fork here, so each batch is run in a different process. This is
+ # because otherwise we get a memory "leak" and you can't rebuild very
+ # large databases (however long you have!)
+ pid = Process.fork # XXX this will only work on Unix, tough
+ if pid
+ Process.waitpid(pid)
+ if not $?.success?
+ raise "batch fork child failed, exiting also"
+ end
+ # database connection doesn't survive a fork, rebuild it
+ ActiveRecord::Base.connection.reconnect!
+ else
+ # fully reopen the database each time (with a new object)
+ # (so doc ids and so on aren't preserved across the fork)
+ ActsAsXapian.writable_init(".new")
+ STDOUT.puts("ActsAsXapian.rebuild_index: New batch. #{model_class.to_s} from #{i} to #{i + batch_size} of #{model_class_count} pid #{Process.pid.to_s}") if verbose
+ models = model_class.find(:all, :limit => batch_size, :offset => i, :order => :id)
+ for model in models
+ STDOUT.puts("ActsAsXapian.rebuild_index #{model_class} #{model.id}") if verbose
+ model.xapian_index
+ end
+ # make sure everything is written
+ ActsAsXapian.writable_db.flush
+ # database connection won't survive a fork, so shut it down
+ ActiveRecord::Base.connection.disconnect!
+ # brutal exit, so other shutdown code not run (for speed and safety)
+ Kernel.exit! 0
end
+
end
- end
end
-
- ActsAsXapian.writable_db.flush
# Rename into place
old_path = ActsAsXapian.db_path
@@ -683,8 +707,7 @@ module ActsAsXapian
doc.add_term("I" + doc.data)
if self.xapian_options[:terms]
for term in self.xapian_options[:terms]
- ActsAsXapian.term_generator.increase_termpos # stop phrases spanning different text fields
- ActsAsXapian.term_generator.index_text(xapian_value(term[0]), 1, term[1])
+ doc.add_term(term[1] + xapian_value(term[0]))
end
end
if self.xapian_options[:values]
diff --git a/vendor/ruby-msg/ChangeLog b/vendor/ruby-msg/ChangeLog
new file mode 100644
index 000000000..fb502127a
--- /dev/null
+++ b/vendor/ruby-msg/ChangeLog
@@ -0,0 +1,82 @@
+== 1.4.0 / 2008-10-12
+
+- Initial simple msg test case.
+- Update TODO, stripping out all the redundant ole stuff.
+- Fix property set guids to use the new Ole::Types::Clsid type.
+- Add block form of Msg.open
+- Fix file requires for running tests individually.
+- Update pst RangesIO subclasses for changes in ruby-ole.
+- Merge initial pst reading code (converted from libpst).
+- Pretty big pst refactoring, adding initial outlook 2003 pst support.
+- Flesh out move to mapi to clean up the way pst hijacks the msg
+ classes currently.
+- Add a ChangeLog :).
+- Update README, by converting Home.wiki with wiki2rdoc converter.
+- Separate out generic mapi object code from msg code, and separate out
+ conversion code.
+- Add decent set of Mapi and Msg unit tests, approaching ~55% code coverage,
+ not including pst.
+- Add TMail note conversion alternative, to eventually allow removal of
+ custom Mime class.
+- Expose experimental pst support through renamed mapitool program.
+
+== 1.3.1 / 2007-08-21
+
+- Add fix for issue #2, and #4.
+- Move ole code to ruby-ole project, and depend on it.
+
+== 1.2.17 / 2007-05-13
+
+(This was last release before splitting out ruby-ole. subsequent bug fix
+point releases 1-3 were made directly on the gem, not reflected in the
+repository, though the fixes were also forward-ported.)
+
+- Update Ole::Storage backend, finalising api for split to separate
+ library.
+
+== 1.2.16 / 2007-04-28
+
+- Some minor fixes to msg parser.
+- Extending RTF and body conversion support.
+- Initial look at possible wmf conversion for embedded images.
+- Add initial cli converter tool
+- Add rdoc to ole/storage, and msg/properties
+- Add streaming IO support to Ole::Storage, and use it in Msg::Properties
+- Updates to test cases
+- Add README, and update TODO
+- Convert rtf support tools in c to small ruby class.
+- Merge preliminary write support for Ole::Storage, as well as preliminary
+ filesystem api.
+
+== 1.2.13 / 2007-01-22
+
+- Nested msg support
+
+== 1.2.10 / 2007-01-21
+
+- Add initial vcard support.
+- Implement a named properties map, for vcard conversion.
+- Add orderedhash to Mime for keeping header order
+- Fix line endings in lib/mime
+- First released version
+
+== <= 1.2.9 / 2007-01-11..2007-01-19
+
+(Haven't bothered to note exact versions and dates - nothing here was released.
+can look at history of lib/msg.rb to see exact VERSION at each commit.)
+
+- Merged most of the named property work.
+- Added some test files.
+- Update svn:ignore, to exclude test messages and ole files which I can't
+ release. Need to get some clean files for use in test cases.
+ Also excluding source to the mapitags files for the moment.
+ A lot of it is not redistributable
+- Added a converter to extract embedded html in rtf. Downloaded somewhere,
+ source unknown.
+- Minor fix to ole/storage.rb, after new OleDir#type behaviour
+- Imported support.rb, replacing previously required std.rb
+- Added initial support for parsing times in Msg::Properties.
+- Imported some rtf decompression code and minor updates.
+- Cleaned up the ole class a bit
+- Fixed OleDir#data method using sb_blocks map (see POLE).
+
diff --git a/vendor/ruby-msg/FIXES b/vendor/ruby-msg/FIXES
new file mode 100644
index 000000000..7a094b437
--- /dev/null
+++ b/vendor/ruby-msg/FIXES
@@ -0,0 +1,56 @@
+FIXES
+
+recent fixes based on importing results into evolution
+
+1. was running into some issue with base64 encoded message/rfc822 attachments displaying
+ as empty. encoding them as plain solved the issue (odd).
+
+2. problem with a large percentage of emails, not displaying as mime. turned out to be
+ all received from blackberry. further, turned out there was 2 content-type headers,
+ "Content-Type", which I add, and "Content-type". normally my override works, but I
+ need to handle it case insensitvely it would appear. more tricky, whats the story
+ with these. fixing that will probably fix that whole class of issues there.
+ evolution was renaming my second content type as X-Invalid-Content-Type or something.
+
+3. another interesting one. had content-transfer-encoding set in the transport message
+ headers. it was set to base64. i didn't override that, so evolution "decoded" my
+ plaintext message into complete garbage.
+ fix - delete content-transfer-encoding.
+
+4. added content-location and content-id output in the mime handling of attachments
+ to get some inline html/image mails to work properly.
+ further, the containing mime content-type must be multipart/related, not multipart/mixed,
+ at least for evolution, in order for the images to appear inline.
+ could still improve in this area. if someone drags and drops in an image, it may
+ be inline in the rtf version, but exchanges generates crappy html such that the image
+ doesn't display inline. maybe i should correct the html output in these cases as i'm
+ throwing away the rtf version.
+
+5. note you may need wingdings installed. i had a lot of L and J appear in messages from
+ outlook users. turns out its smilies in wingdings. i think its only if word is used
+ as email editor and has autotext messing things up.
+
+6. still unsure about how to do my "\r" handling.
+
+7. need to join addresses with , instead of ; i think. evolution only shows the
+ first one otherwise it appears, but all when they are , separated.
+
+8. need to solve ole storage issues with the very large file using extra bat
+ stuff.
+
+9. retest a bit on evolution and thunderbird, and release. tested on a corups
+ of >1000 msg files, so should be starting to get pretty good quality.
+
+10. longer term, things fall into a few basic categories:
+
+- non mail conversions (look further into vcard, ical et al support for other
+ types of msg)
+- further tests and robustness for what i handle now. ie, look into corner
+ cases covered so far, and work on the mime code. fix random charset encoding
+ issues, in the various weird mime ways, do header wrapping etc etc.
+ check fidelity of conversions, and capture some more properties as headers,
+ such as importance which i don't do yet.
+- fix that named property bug. tidy up warnings, exceptions.
+- extend conversion to make better html.
+ this is longer term. as i don't use the rtf, i need to make my html better.
+ emulating some rtf things. harder, not important atm.
diff --git a/vendor/ruby-msg/README b/vendor/ruby-msg/README
new file mode 100644
index 000000000..bd16dfcc4
--- /dev/null
+++ b/vendor/ruby-msg/README
@@ -0,0 +1,128 @@
+= Introduction
+
+Generally, the goal of the project is to enable the conversion of
+msg and pst files into standards based formats, without reliance on
+outlook, or any platform dependencies. In fact its currently <em>pure
+ruby</em>, so it should be easy to get running.
+
+It is targeted at people who want to migrate their PIM data from outlook,
+converting msg and pst files into rfc2822 emails, vCard contacts,
+iCalendar appointments etc. However, it also aims to be a fairly complete
+mapi message store manipulation library, providing a sane model for
+(currently read-only) access to msg and pst files (message stores).
+
+I am happy to accept patches, give commit bits etc.
+
+Please let me know how it works for you, any feedback would be welcomed.
+
+= Features
+
+Broad features of the project:
+
+* Can be used as a general mapi library, where conversion to and working
+ on a standard format doesn't make sense.
+
+* Supports conversion of messages to standard formats, like rfc2822
+ emails, vCard, etc.
+
+* Well commented, and easily extended.
+
+* Basic RTF converter, for providing a readable body when only RTF
+ exists (needs work)
+
+* RTF decompression support included, as well as HTML extraction from
+ RTF where appropriate (both in pure ruby, see <tt>lib/mapi/rtf.rb</tt>)
+
+* Support for mapping property codes to symbolic names, with many
+ included.
+
+Features of the msg format message store:
+
+* Most key .msg structures are understood, and the only the parsing
+ code should require minor tweaks. Most of remaining work is in achieving
+ high-fidelity conversion to standards formats (see [TODO]).
+
+* Supports both types of property storage (large ones in +substg+
+ files, and small ones in the +properties+ file.
+
+* Complete support for named properties in different GUID namespaces.
+
+* Initial support for handling embedded ole files, converting nested
+ .msg files to message/rfc822 attachments, and serializing others
+ as ole file attachments (allows you to view embedded excel for example).
+
+Features of the pst format message store:
+
+* Handles both Outlook 1997 & 2003 format pst files, both with no-
+ and "compressible-" encryption.
+
+* Understanding of the file format is still very superficial.
+
+= Usage
+
+At the command line, it is simple to convert individual msg or pst
+files to .eml, or to convert a batch to an mbox format file. See mapitool
+help for details:
+
+ mapitool -si some_email.msg > some_email.eml
+ mapitool -s *.msg > mbox
+
+There is also a fairly complete and easy to use high level library
+access:
+
+ require 'mapi/msg'
+
+ msg = Mapi::Msg.open filename
+
+ # access to the 3 main data stores, if you want to poke with the msg
+ # internals
+ msg.recipients
+ # => [#<Recipient:'\'Marley, Bob\' <bob.marley@gmail.com>'>]
+ msg.attachments
+ # => [#<Attachment filename='blah1.tif'>, #<Attachment filename='blah2.tif'>]
+ msg.properties
+ # => #<Properties ... normalized_subject='Testing' ...
+ # creation_time=#<DateTime: 2454042.45074714,0,2299161> ...>
+
+To completely abstract away all msg peculiarities, convert the msg
+to a mime object. The message as a whole, and some of its main parts
+support conversion to mime objects.
+
+ msg.attachments.first.to_mime
+ # => #<Mime content_type='application/octet-stream'>
+ mime = msg.to_mime
+ puts mime.to_tree
+ # =>
+ - #<Mime content_type='multipart/mixed'>
+ |- #<Mime content_type='multipart/alternative'>
+ | |- #<Mime content_type='text/plain'>
+ | \- #<Mime content_type='text/html'>
+ |- #<Mime content_type='application/octet-stream'>
+ \- #<Mime content_type='application/octet-stream'>
+
+ # convert mime object to serialised form,
+ # inclusive of attachments etc. (not ideal in memory, but its wip).
+ puts mime.to_s
+
+= Thanks
+
+* The initial implementation of parsing msg files was based primarily
+ on msgconvert.pl[http://www.matijs.net/software/msgconv/].
+
+* The basis for the outlook 97 pst file was the source to +libpst+.
+
+* The code for rtf decompression was implemented by inspecting the
+ algorithm used in the +JTNEF+ project.
+
+= Other
+
+For more information, see
+
+* TODO
+
+* MsgDetails[http://code.google.com/p/ruby-msg/wiki/MsgDetails]
+
+* PstDetails[http://code.google.com/p/ruby-msg/wiki/PstDetails]
+
+* OleDetails[http://code.google.com/p/ruby-ole/wiki/OleDetails]
+
diff --git a/vendor/ruby-msg/Rakefile b/vendor/ruby-msg/Rakefile
new file mode 100644
index 000000000..066ca3741
--- /dev/null
+++ b/vendor/ruby-msg/Rakefile
@@ -0,0 +1,77 @@
+require 'rake/rdoctask'
+require 'rake/testtask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+
+require 'rbconfig'
+require 'fileutils'
+
+$:.unshift 'lib'
+
+require 'mapi/msg'
+
+PKG_NAME = 'ruby-msg'
+PKG_VERSION = Mapi::VERSION
+
+task :default => [:test]
+
+Rake::TestTask.new(:test) do |t|
+ t.test_files = FileList["test/test_*.rb"] - ['test/test_pst.rb']
+ t.warning = false
+ t.verbose = true
+end
+
+begin
+ require 'rcov/rcovtask'
+ # NOTE: this will not do anything until you add some tests
+ desc "Create a cross-referenced code coverage report"
+ Rcov::RcovTask.new do |t|
+ t.test_files = FileList['test/test*.rb']
+ t.ruby_opts << "-Ilib" # in order to use this rcov
+ t.rcov_opts << "--xrefs" # comment to disable cross-references
+ t.rcov_opts << "--exclude /usr/local/lib/site_ruby"
+ t.verbose = true
+ end
+rescue LoadError
+ # Rcov not available
+end
+
+Rake::RDocTask.new do |t|
+ t.rdoc_dir = 'doc'
+ t.title = "#{PKG_NAME} documentation"
+ t.options += %w[--main README --line-numbers --inline-source --tab-width 2]
+ t.rdoc_files.include 'lib/**/*.rb'
+ t.rdoc_files.include 'README'
+end
+
+spec = Gem::Specification.new do |s|
+ s.name = PKG_NAME
+ s.version = PKG_VERSION
+ s.summary = %q{Ruby Msg library.}
+ s.description = %q{A library for reading and converting Outlook msg and pst files (mapi message stores).}
+ s.authors = ["Charles Lowe"]
+ s.email = %q{aquasync@gmail.com}
+ s.homepage = %q{http://code.google.com/p/ruby-msg}
+ s.rubyforge_project = %q{ruby-msg}
+
+ s.executables = ['mapitool']
+ s.files = FileList['data/*.yaml', 'Rakefile', 'README', 'FIXES']
+ s.files += FileList['lib/**/*.rb', 'test/test_*.rb', 'bin/*']
+
+ s.has_rdoc = true
+ s.extra_rdoc_files = ['README']
+ s.rdoc_options += ['--main', 'README',
+ '--title', "#{PKG_NAME} documentation",
+ '--tab-width', '2']
+
+ s.add_dependency 'ruby-ole', '>=1.2.8'
+ s.add_dependency 'vpim', '>=0.360'
+end
+
+Rake::GemPackageTask.new(spec) do |p|
+ p.gem_spec = spec
+ p.need_tar = false #true
+ p.need_zip = false
+ p.package_dir = 'build'
+end
+
diff --git a/vendor/ruby-msg/TODO b/vendor/ruby-msg/TODO
new file mode 100644
index 000000000..2e4309c21
--- /dev/null
+++ b/vendor/ruby-msg/TODO
@@ -0,0 +1,184 @@
+= Newer Msg
+
+* a lot of the doc is out of sync given the Mapi:: changes lately. need to
+ fix.
+
+* extend msgtool with support for the other kinds of msg files. things like,
+ - dump properties to yaml / xml (with option on how the keys should be dumped).
+ should be fairly easy to implement. hash, with array of attach & recips.
+ - just write out the mime type
+ - convert to the automatically guessed output type, also allowing an override
+ of some sort. have a batch mode that converts all arguments, using automatic
+ extensions, eg .vcf, .eml etc.
+ - options regarding preferring rtf / html / txt output for things. like, eg
+ the output for note conversion, will be one of them i guess.
+
+* fix nameid handling for sub Properties objects. needs to inherit top-level @nameid.
+
+* better handling for "Untitled Attachments", or attachments with no filename at all.
+ maybe better handling in general for attached messages. maybe re-write filename with
+ subject.
+
+* do the above 2, then go for a new release.
+ for this release, i want it to be pretty robust, and have better packaging.
+ have the gem install 2 tools - oletool (--tree, --read, --write, --repack)
+ and msgtool (--convert, --dump-attachments etc)
+ fix docs, add option parsing etc etc.
+ submit to the usual gem repositories etc, announce project as usable.
+
+* better handling for other types of embedded ole attachments. try to recognise .wmf to
+ start with. etc. 2 goals, form .eml, and for .rtf output.
+
+* rtf to text support, initially. just simple strip all the crap out approach. maybe html
+ later on. sometimes the only body is rtf, which is problematic. is rtf an allowed body
+ type?
+
+* better encoding support (i'm thinking, check some things like m-dash in the non-wide
+ string type. is it windows codepage?. how to know code page in general? change
+ ENCODER[] to convert these to utf8 i think). I'm trying to just move everything to utf8.
+ then i need to make sure the mime side of that holds up.
+
+* certain parsing still not correct, especially small properties directory,
+ things like bools, multiavlue shorts etc.
+
+* test cases for msg/properties.rb msg/rtf.rb and mime.rb
+* regarding the mime fix ups. check out:
+
+- http://rubyforge.org/projects/rubymail/, and
+- http://rubyforge.org/projects/mime-alt-lite/
+
+both haven't been touched in 2 years though.
+
+* get some sort of working From: header
+ there doesn't appear to be a way to get an smtp address for the sender in an
+ internal mail. you need to query the exchanger server, using the sender_entryid
+ as far as i can tell.
+ you may also get these so called "X.400" addresses for external recipients
+ that are on the GAL (Global Address List), as custom recipients.
+ (mostly complete. better way?)
+* check lots of little details: encoding issues with code pages etc other
+ than ascii stuff. consider Msg, Mime, Vcard etc, to check its clean. check
+ timezones, inaccuracies in date handling.
+* http://msdn2.microsoft.com/en-us/library/ms526761.aspx
+ import guids for these?
+PS_ROUTING_EMAIL_ADDRESSES
+PS_ROUTING_ADDRTYPE
+PS_ROUTING_SEARCH_KEY
+PS_ROUTING_DISPLAY_NAME
+PS_ROUTING_ENTRYID
+* check this thing i wrote:
+ creating ones in ps_mapi works strangely, as ps_mapi is the top levels ones.
+ don't get entries in nameid. as should match the definitions.
+ however still get allocated an 8 number. that number becomes permanent...
+* do something about the message class-specific range, 0x6800 through 0x7FFF.
+* multivalue encoding may explain some of the unknown data in properties objs
+* get some clean test msgs from somewhere
+* tackle other types of msgs, such as appointments, contacts etc, converting
+ to their appropriate standard types too.
+ would like to look at this next. see about the content specific range
+ http://en.wikipedia.org/wiki/ICalendar
+ is it better/easier to go through micro formats? there are libraries for ruby
+ already i think. check this out. although using IMC standards seem to make
+ more sense. encoding issues?
+ try contact => vcf,
+ note => ?? icalendar. VTODO? / VJOURNAL?
+ appointment => VEVENT?
+ (initial try done. really basic vcard output supported. not difficult)
+* ole code is suppopsed to be able to return guids for something too. supporting
+ all this probably means creating a new file for ole/types.rb, containing all
+ the various classes, and binary conversion code.
+* some dubiously useful info about some unknown codes i wrote:
+unknown property 5ff6
+ * appears to be equal to display_name, and transmitable_display_name
+unknown property 5ff7
+ * recipient.properties[0x5ff7].upcase == recipient.properties.entryid
+ is equivalent, but not all uppercase.
+ everything else is upper though. maybe a displayname kind of thing.
+* common relationship
+ CGI.escape(msg.properties.subject.strip).tr('+', ' ') + '.EML' == msg.properties.url_comp_name
+
+(28 bytes binary data and msg.properties.sender_email_address and "\000")
+entryids are a strange format. for internal or from exchange whatever, they have that
+EX:/O=XXXXXX/...
+otherwise, they may have SMTP in them.
+ such as msg.properties.sent_representing_search_key
+ == "SMTP:SOMEGUY@XXX.COM\000"
+ but Ole::UTF16_TO_UTF8[msg2.properties.sender_entryid[/.*\000\000(.+)\000/, 1][0..-2]]
+ == "SomeGuy@XXX.COM"
+ for external people, entry ids have displayname and address.
+
+longer term, i want to investigate the overlap with PST stuff, like libpst,
+which seems to be another kind of mapi tag property storage, and try to
+understand the relationship with existing TNEF work also.
+
+hmmmm for future work:
+http://blogs.msdn.com/stephen_griffin/archive/2005/10/25/484656.aspx
+
+
+= Older
+
+- set 'From' in Msg#populate_headers.
+ Notes:
+ # ways to get the sender of a mail.
+ # if external, you can do this (different for internal).
+ name, protocol, email = Ole::UTF16_TO_UTF8[msg.props.sender_entryid[24..-1]].split(/\x00/)
+ # here is an excerpt from a yaml dump.
+ # need to consider how to get it. also when its a draft, and other stuff.
+ creator_name:
+ sent_representing_name:
+ last_modifier_name:
+ sender_email_address:
+ sent_representing_email_address:
+ sender_name:
+- fill out some of the tag holes. mostly done
+- properly integrate rtf decompression, and the html conversion from rtf
+- figure out some things, like entryids, and the properties directories,
+ and the ntsecurity data 0e27.
+ http://peach.ease.lsoft.com/scripts/wa.exe?A2=ind0207&L=mapi-l&P=24515
+ "In case anybody is interested, Exchange stores PR_NT_SECURITY_DESCRIPTOR as a header plus the regular self=-relative SECURITY_DESCRIPTOR structure. The first two bytes of the header (WORD) is the length of the header; Read these two bytes to find out how many bytes you must skip to get to the real data. Many thanks to Stephen Griffin for this info."
+ using outlook spy gives an actual dump. for example:
+ <<
+ Control: SE_DACL_AUTO_INHERITED | SE_DACL_DEFAULTED | SE_DACL_PRESENT | SE_GROUP_DEFAULTED | SE_OWNER_DEFAULTED | SE_SACL_AUTO_INHERITED | SE_SACL_DEFAULTED | SE_SELF_RELATIVE
+ Owner:
+ SID: S-1-5-21-1004336348-602609370-725345543-44726
+ Name: lowecha
+ DomainName: XXX
+ Group:
+ SID: S-1-5-21-1004336348-602609370-725345543-513
+ Name: Domain Users
+ DomainName: XXX
+ Dacl:
+ Header:
+ AceType: ACCESS_DENIED_ACE_TYPE
+ AceFlags: INHERITED_ACE
+ Mask: fsdrightReadBody (fsdrightListContents) | fsdrightWriteBody (fsdrightCreateItem) | fsdrightAppendMsg (fsdrightCreateContainer) | fsdrightReadProperty | fsdrightWriteProperty | fsdrightExecute | fsdrightReadAttributes | fsdrightWriteAttributes | fsdrightWriteOwnProperty | fsdrightDeleteOwnItem | fsdrightViewItem | fsdrightWriteSD | fsdrightDelete | fsdrightWriteOwner | fsdrightReadControl | fsdrightSynchronize
+ Sid:
+ SID: S-1-5-7
+ Name: ANONYMOUS LOGON
+ DomainName: NT AUTHORITY
+ >>
+ Not something i care about at the moment.
+
+- conversion of inline images.
+ Content-Location, cid: urls etc etc.
+ what would be cool, is conversion of outlooks text/rtf's only real "feature" over
+ text/html - convert inline attachments to be <a href> links, using cid: urls to the
+ actual content data, and using an <img with cid: url to a converted image from the
+ attach_rendering property (image data), along with the text itself. (although i think
+ the rendering may actually include the text ??. that would explain why its always clipped.
+ can these be used for contact pictures too?
+- entryid format cf. entry_id.h. another serialized structure.
+ entryids are for the addressbook connection. EMS (exchange message something), AB
+ address book. MUIDEMSAB. makes sense.
+
+mapidefs.h:
+
+ 174 /* Types of message receivers */
+ 175 #ifndef MAPI_ORIG
+ 176 #define MAPI_ORIG 0 /* The original author */
+ 177 #define MAPI_TO 1 /* The primary message receiver */
+ 178 #define MAPI_CC 2 /* A carbon copy receiver */
+ 179 #define MAPI_BCC 3 /* A blind carbon copy receiver */
+ 180 #define MAPI_P1 0x10000000 /* A message resend */
+ 181 #define MAPI_SUBMITTED 0x80000000 /* This message has already been sent */
+ 182 #endif
diff --git a/vendor/ruby-msg/bin/mapitool b/vendor/ruby-msg/bin/mapitool
new file mode 100755
index 000000000..79824daa4
--- /dev/null
+++ b/vendor/ruby-msg/bin/mapitool
@@ -0,0 +1,195 @@
+#! /usr/bin/ruby
+
+$:.unshift File.dirname(__FILE__) + '/../lib'
+
+require 'optparse'
+require 'rubygems'
+require 'mapi/msg'
+require 'mapi/pst'
+require 'mapi/convert'
+require 'time'
+
+class Mapitool
+ attr_reader :files, :opts
+ def initialize files, opts
+ @files, @opts = files, opts
+ seen_pst = false
+ raise ArgumentError, 'Must specify 1 or more input files.' if files.empty?
+ files.map! do |f|
+ ext = File.extname(f.downcase)[1..-1]
+ raise ArgumentError, 'Unsupported file type - %s' % f unless ext =~ /^(msg|pst)$/
+ raise ArgumentError, 'Expermiental pst support not enabled' if ext == 'pst' and !opts[:enable_pst]
+ [ext.to_sym, f]
+ end
+ if dir = opts[:output_dir]
+ Dir.mkdir(dir) unless File.directory?(dir)
+ end
+ end
+
+ def each_message(&block)
+ files.each do |format, filename|
+ if format == :pst
+ if filter_path = opts[:filter_path]
+ filter_path = filter_path.tr("\\", '/').gsub(/\/+/, '/').sub(/^\//, '').sub(/\/$/, '')
+ end
+ open filename do |io|
+ pst = Mapi::Pst.new io
+ pst.each do |message|
+ next unless message.type == :message
+ if filter_path
+ next unless message.path =~ /^#{Regexp.quote filter_path}(\/|$)/i
+ end
+ yield message
+ end
+ end
+ else
+ Mapi::Msg.open filename, &block
+ end
+ end
+ end
+
+ def run
+ each_message(&method(:process_message))
+ end
+
+ def make_unique filename
+ @map ||= {}
+ return @map[filename] if !opts[:individual] and @map[filename]
+ try = filename
+ i = 1
+ try = filename.gsub(/(\.[^.]+)$/, ".#{i += 1}\\1") while File.exist?(try)
+ @map[filename] = try
+ try
+ end
+
+ def process_message message
+ # TODO make this more informative
+ mime_type = message.mime_type
+ return unless pair = Mapi::Message::CONVERSION_MAP[mime_type]
+
+ combined_map = {
+ 'eml' => 'Mail.mbox',
+ 'vcf' => 'Contacts.vcf',
+ 'txt' => 'Posts.txt'
+ }
+
+ # TODO handle merged mode, pst, etc etc...
+ case message
+ when Mapi::Msg
+ if opts[:individual]
+ filename = message.root.ole.io.path.gsub(/msg$/i, pair.last)
+ else
+ filename = combined_map[pair.last] or raise NotImplementedError
+ end
+ when Mapi::Pst::Item
+ if opts[:individual]
+ filename = "#{message.subject.tr ' ', '_'}.#{pair.last}".gsub(/[^A-Za-z0-9.()\[\]{}-]/, '_')
+ else
+ filename = combined_map[pair.last] or raise NotImplementedError
+ filename = (message.path.tr(' /', '_.').gsub(/[^A-Za-z0-9.()\[\]{}-]/, '_') + '.' + File.extname(filename)).squeeze('.')
+ end
+ dir = File.dirname(message.instance_variable_get(:@desc).pst.io.path)
+ filename = File.join dir, filename
+ else
+ raise
+ end
+
+ if dir = opts[:output_dir]
+ filename = File.join dir, File.basename(filename)
+ end
+
+ filename = make_unique filename
+
+ write_message = proc do |f|
+ data = message.send(pair.first).to_s
+ if !opts[:individual] and pair.last == 'eml'
+ # we do the append > style mbox quoting (mboxrd i think its called), as it
+ # is the only one that can be robuslty un-quoted. evolution doesn't use this!
+ f.puts "From mapitool@localhost #{Time.now.rfc2822}"
+ #munge_headers mime, opts
+ data.each do |line|
+ if line =~ /^>*From /o
+ f.print '>' + line
+ else
+ f.print line
+ end
+ end
+ else
+ f.write data
+ end
+ end
+
+ if opts[:stdout]
+ write_message[STDOUT]
+ else
+ open filename, 'a', &write_message
+ end
+ end
+
+ def munge_headers mime, opts
+ opts[:header_defaults].each do |s|
+ key, val = s.match(/(.*?):\s+(.*)/)[1..-1]
+ mime.headers[key] = [val] if mime.headers[key].empty?
+ end
+ end
+end
+
+def mapitool
+ opts = {:verbose => false, :action => :convert, :header_defaults => []}
+ op = OptionParser.new do |op|
+ op.banner = "Usage: mapitool [options] [files]"
+ #op.separator ''
+ #op.on('-c', '--convert', 'Convert input files (default)') { opts[:action] = :convert }
+ op.separator ''
+ op.on('-o', '--output-dir DIR', 'Put all output files in DIR') { |d| opts[:output_dir] = d }
+ op.on('-i', '--[no-]individual', 'Do not combine converted files') { |i| opts[:individual] = i }
+ op.on('-s', '--stdout', 'Write all data to stdout') { opts[:stdout] = true }
+ op.on('-f', '--filter-path PATH', 'Only process pst items in PATH') { |path| opts[:filter_path] = path }
+ op.on( '--enable-pst', 'Turn on experimental PST support') { opts[:enable_pst] = true }
+ #op.on('-d', '--header-default STR', 'Provide a default value for top level mail header') { |hd| opts[:header_defaults] << hd }
+ # --enable-pst
+ op.separator ''
+ op.on('-v', '--[no-]verbose', 'Run verbosely') { |v| opts[:verbose] = v }
+ op.on_tail('-h', '--help', 'Show this message') { puts op; exit }
+ end
+
+ files = op.parse ARGV
+
+ # for windows. see issue #2
+ STDOUT.binmode
+
+ Mapi::Log.level = Ole::Log.level = opts[:verbose] ? Logger::WARN : Logger::FATAL
+
+ tool = begin
+ Mapitool.new(files, opts)
+ rescue ArgumentError
+ puts $!
+ puts op
+ exit 1
+ end
+
+ tool.run
+end
+
+mapitool
+
+__END__
+
+mapitool [options] [files]
+
+files is a list of *.msg & *.pst files.
+
+one of the options should be some sort of path filter to apply to pst items.
+
+--filter-path=
+--filter-type=eml,vcf
+
+with that out of the way, the entire list of files can be converted into a
+list of items (with meta data about the source).
+
+--convert
+--[no-]separate one output file per item or combined output
+--stdout
+--output-dir=.
+
+
diff --git a/vendor/ruby-msg/contrib/rtf2html.c b/vendor/ruby-msg/contrib/rtf2html.c
new file mode 100644
index 000000000..937e22ff1
--- /dev/null
+++ b/vendor/ruby-msg/contrib/rtf2html.c
@@ -0,0 +1,155 @@
+#include <stdio.h>
+#define bool int
+#define false 0
+#define true 1
+
+// RTF/HTML functions
+// --------------------
+//
+// Sometimes in MAPI, the PR_BODY_HTML property contains the HTML of a message.
+// But more usually, the HTML is encoded inside the RTF body (which you get in the
+// PR_RTF_COMPRESSED property). These routines concern the decoding of the HTML
+// from this RTF body.
+//
+// An encoded htmlrtf file is a valid RTF document, but which contains additional
+// html markup information in its comments, and sometimes contains the equivalent
+// rtf markup outside the comments. Therefore, when it is displayed by a plain
+// simple RTF reader, the html comments are ignored and only the rtf markup has
+// effect. Typically, this rtf markup is not as rich as the html markup would have been.
+// But for an html-aware reader (such as the code below), we can ignore all the
+// rtf markup, and extract the html markup out of the comments, and get a valid
+// html document.
+//
+// There are actually two kinds of html markup in comments. Most of them are
+// prefixed by "\*\htmltagNNN", for some number NNN. But sometimes there's one
+// prefixed by "\*\mhtmltagNNN" followed by "\*\htmltagNNN". In this case,
+// the two are equivalent, but the m-tag is for a MIME Multipart/Mixed Message
+// and contains tags that refer to content-ids (e.g. img src="cid:072344a7")
+// while the normal tag just refers to a name (e.g. img src="fred.jpg")
+// The code below keeps the m-tag and discards the normal tag.
+// If there are any m-tags like this, then the message also contains an
+// attachment with a PR_CONTENT_ID property e.g. "072344a7". Actually,
+// sometimes the m-tag is e.g. img src="http://outlook/welcome.html" and the
+// attachment has a PR_CONTENT_LOCATION "http://outlook/welcome.html" instead
+// of a PR_CONTENT_ID.
+//
+// This code is experimental. It works on my own message archive, of about
+// a thousand html-encoded messages, received in Outlook97 and Outlook2000
+// and OutlookXP. But I can't guarantee that it will work on all rtf-encoded
+// messages. Indeed, it used to be the case that people would simply stick
+// {\fromhtml at the start of an html document, and } at the end, and send
+// this as RTF. If someone did this, then it will almost work in my function
+// but not quite. (Because I ignore \r and \n, and respect only \par. Thus,
+// any linefeeds in the erroneous encoded-html will be ignored.)
+
+
+
+
+
+// ISRTFHTML -- Given an uncompressed RTF body of the message, this
+// function tells you whether it encodes some html.
+// [in] (buf,*len) indicate the start and length of the uncompressed RTF body.
+// [return-value] true or false, for whether it really does encode some html
+bool isrtfhtml(const char *buf,unsigned int len)
+{ // We look for the words "\fromhtml" somewhere in the file.
+ // If the rtf encodes text rather than html, then instead
+ // it will only find "\fromtext".
+ const char *c;
+ for (c=buf; c<buf+len; c++)
+ { if (strncmp(c,"\\from",5)==0) return strncmp(c,"\\fromhtml",9)==0;
+ }
+ return false;
+}
+
+
+
+
+// DECODERTFHTML -- Given an uncompressed RTF body of the message,
+// and assuming that it contains encoded-html, this function
+// turns it onto regular html.
+// [in] (buf,*len) indicate the start and length of the uncompressed RTF body.
+// [out] the buffer is overwritten with the HTML version, null-terminated,
+// and *len indicates the length of this HTML.
+//
+// Notes: (1) because of how the encoding works, the HTML version is necessarily
+// shorter than the encoded version. That's why it's safe for the function to
+// place the decoded html in the same buffer that formerly held the encoded stuff.
+// (2) Some messages include characters \'XX, where XX is a hexedecimal number.
+// This function simply converts this into ASCII. The conversion will only make
+// sense if the right code-page is being used. I don't know how rtf specifies which
+// code page it wants.
+// (3) By experiment, I discovered that \pntext{..} and \liN and \fi-N are RTF
+// markup that should be removed. There might be other RTF markup that should
+// also be removed. But I don't know what else.
+//
+void decodertfhtml(char *buf,unsigned int *len)
+{ // c -- pointer to where we're reading from
+ // d -- pointer to where we're writing to. Invariant: d<c
+ // max -- how far we can read from (i.e. to the end of the original rtf)
+ // ignore_tag -- stores 'N': after \mhtmlN, we will ignore the subsequent \htmlN.
+ char *c=buf, *max=buf+*len, *d=buf; int ignore_tag=-1;
+ // First, we skip forwards to the first \htmltag.
+ while (c<max && strncmp(c,"{\\*\\htmltag",11)!=0) c++;
+ //
+ // Now work through the document. Our plan is as follows:
+ // * Ignore { and }. These are part of RTF markup.
+ // * Ignore \htmlrtf...\htmlrtf0. This is how RTF keeps its equivalent markup separate from the html.
+ // * Ignore \r and \n. The real carriage returns are stored in \par tags.
+ // * Ignore \pntext{..} and \liN and \fi-N. These are RTF junk.
+ // * Convert \par and \tab into \r\n and \t
+ // * Convert \'XX into the ascii character indicated by the hex number XX
+ // * Convert \{ and \} into { and }. This is how RTF escapes its curly braces.
+ // * When we get \*\mhtmltagN, keep the tag, but ignore the subsequent \*\htmltagN
+ // * When we get \*\htmltagN, keep the tag as long as it isn't subsequent to a \*\mhtmltagN
+ // * All other text should be kept as it is.
+ while (c<max)
+ { if (*c=='{') c++;
+ else if (*c=='}') c++;
+ else if (strncmp(c,"\\*\\htmltag",10)==0)
+ { c+=10; int tag=0; while (*c>='0' && *c<='9') {tag=tag*10+*c-'0'; c++;}
+ if (*c==' ') c++;
+ if (tag==ignore_tag) {while (c<max && *c!='}') c++; if (*c=='}') c++;}
+ ignore_tag=-1;
+ }
+ else if (strncmp(c,"\\*\\mhtmltag",11)==0)
+ { c+=11; int tag=0; while (*c>='0' && *c<='9') {tag=tag*10+*c-'0'; c++;}
+ if (*c==' ') c++;
+ ignore_tag=tag;
+ }
+ else if (strncmp(c,"\\par",4)==0) {strcpy(d,"\r\n"); d+=2; c+=4; if (*c==' ') c++;}
+ else if (strncmp(c,"\\tab",4)==0) {strcpy(d," "); d+=3; c+=4; if (*c==' ') c++;}
+ else if (strncmp(c,"\\li",3)==0)
+ { c+=3; while (*c>='0' && *c<='9') c++; if (*c==' ') c++;
+ }
+ else if (strncmp(c,"\\fi-",4)==0)
+ { c+=4; while (*c>='0' && *c<='9') c++; if (*c==' ') c++;
+ }
+ else if (strncmp(c,"\\'",2)==0)
+ { unsigned int hi=c[2], lo=c[3];
+ if (hi>='0' && hi<='9') hi-='0'; else if (hi>='A' && hi<='Z') hi-='A'; else if (hi>='a' && hi<='z') hi-='a';
+ if (lo>='0' && lo<='9') lo-='0'; else if (lo>='A' && lo<='Z') lo-='A'; else if (lo>='a' && lo<='z') lo-='a';
+ *((unsigned char*)d) = (unsigned char)(hi*16+lo);
+ c+=4; d++;
+ }
+ else if (strncmp(c,"\\pntext",7)==0) {c+=7; while (c<max && *c!='}') c++;}
+ else if (strncmp(c,"\\htmlrtf",8)==0)
+ { c++; while (c<max && strncmp(c,"\\htmlrtf0",9)!=0) c++;
+ if (c<max) c+=9; if (*c==' ') c++;
+ }
+ else if (*c=='\r' || *c=='\n') c++;
+ else if (strncmp(c,"\\{",2)==0) {*d='{'; d++; c+=2;}
+ else if (strncmp(c,"\\}",2)==0) {*d='}'; d++; c+=2;}
+ else {*d=*c; c++; d++;}
+ }
+ *d=0; d++;
+ *len = d-buf;
+}
+
+
+void main()
+{
+ unsigned char buf[1024*1024];
+ int len = fread(buf, 1, 1024*1024, stdin);
+ decodertfhtml(buf, &len);
+ fwrite(buf, 1, len, stdout);
+}
diff --git a/vendor/ruby-msg/contrib/rtfdecompr.c b/vendor/ruby-msg/contrib/rtfdecompr.c
new file mode 100644
index 000000000..633d50286
--- /dev/null
+++ b/vendor/ruby-msg/contrib/rtfdecompr.c
@@ -0,0 +1,105 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void decompress_rtf(FILE *srcf)
+{
+// #define prebuf_len (sizeof(prebuf))
+// static unsigned char prebuf[] =
+
+ // the window of decompressed bytes that can be referenced for copies.
+ // moved to this rather than indexing directly into output for streaming.
+ // circular buffer.
+ // because we use single-function call approach, no need for copy.
+ // if using libstream-3, i would have a few options. i would be part of
+ // the filter interface, which doesn't care if it is reading or writing,
+ // all it knows about is its input and output buffers. we can't just
+ // flush some data to the output buffer in that scenario, so we would need
+ // to keep the window around. we also can't guarantee availability of that
+ // buffer. so, we would probably have a instance member which would be
+ // this ->
+ unsigned char buf[4096] =
+ "{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}"
+ "{\\f0\\fnil \\froman \\fswiss \\fmodern \\fscript "
+ "\\fdecor MS Sans SerifSymbolArialTimes New RomanCourier"
+ "{\\colortbl\\red0\\green0\\blue0\n\r\\par "
+ "\\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx";
+
+ #define BUF_MASK 4095
+
+ int wp = strlen((char *)buf);
+
+ unsigned char *dst; // destination for uncompressed bytes
+ int in = 0; // current position in src array
+ int out = 0; // current position in dst array
+
+ unsigned char hdr[16];
+ int got;
+ // get header fields (as defined in RTFLIB.H)
+ got = fread(hdr, 1, 16, srcf);
+ if (got != 16) {
+ printf("Invalid compressed-RTF header\n");
+ exit(1);
+ }
+
+ int compr_size = *(unsigned int *)(hdr);
+ int uncompr_size = *(unsigned int *)(hdr + 4);
+ int magic = *(unsigned int *)(hdr + 8);
+ long crc32 = *(unsigned int *)(hdr + 12);
+
+ unsigned char *x, *y;;
+ unsigned char *src = malloc(compr_size - 12); // includes the 3 header fields
+ y = src;
+ x = src + compr_size - 12;
+ got = fread(src, 1, compr_size - 12, srcf);
+ if (got != compr_size - 12) {
+ printf("compressed-RTF data size mismatch (%d != %d)\n", got, compr_size - 12);
+ exit(1);
+ }
+ // shouldn't be any more than that
+ got = fread(dst, 1, 16, srcf);
+ if (got > 0) {
+ printf("warning: data after the size\n");
+ }
+
+ // process the data
+ if (magic == 0x414c454d) { // magic number that identifies the stream as a uncompressed stream
+ dst = malloc(uncompr_size);
+ memcpy(dst, src, uncompr_size);
+ }
+ else if (magic == 0x75465a4c) { // magic number that identifies the stream as a compressed stream
+ out = 0; //strlen(prebuf);
+ int dst_len;
+ dst = malloc(dst_len = uncompr_size);
+
+ int flagCount = 0;
+ int flags = 0;
+ while (out < dst_len && src < x) {
+ // each flag byte flags 8 literals/references, 1 per bit
+ flags = (flagCount++ % 8 == 0) ? *src++ : flags >> 1;
+ if (flags & 1) { // each flag bit is 1 for reference, 0 for literal
+ int rp = *src++;
+ int l = *src++;
+ //offset is a 12 byte number. 2^12 is 4096, so thats fine
+ rp = (rp << 4) | (l >> 4); // the offset relative to block start
+ l = (l & 0xf) + 2; // the number of bytes to copy
+ int e = rp + l;
+ while (rp < e)
+ putchar(buf[wp++ & BUF_MASK] = buf[rp++ & BUF_MASK]);
+ }
+ else putchar(buf[wp++ & BUF_MASK] = *src++);
+ }
+ }
+ else { // unknown magic number
+ printf("Unknown compression type (magic number %04x)", magic);
+ }
+
+ free(y);
+}
+
+int main(int argc, char *argv[])
+{
+ FILE *file = fopen(argv[1], "rb");
+ decompress_rtf(file);
+ fclose(file);
+}
diff --git a/vendor/ruby-msg/contrib/wmf.rb b/vendor/ruby-msg/contrib/wmf.rb
new file mode 100644
index 000000000..531e5fc99
--- /dev/null
+++ b/vendor/ruby-msg/contrib/wmf.rb
@@ -0,0 +1,107 @@
+
+# this file will be used later to enhance the msg conversion.
+
+# doesn't really work very well....
+
+def wmf_getdimensions wmf_data
+ # check if we have a placeable metafile
+ if wmf_data.unpack('L')[0] == 0x9ac6cdd7
+ # do check sum test
+ shorts = wmf_data.unpack 'S11'
+ warn 'bad wmf header checksum' unless shorts.pop == shorts.inject(0) { |a, b| a ^ b }
+ # determine dimensions
+ left, top, right, bottom, twips_per_inch = wmf_data[6, 10].unpack 'S5'
+ p [left, top, right, bottom, twips_per_inch]
+ [right - left, bottom - top].map { |i| (i * 96.0 / twips_per_inch).round }
+ else
+ [nil, nil]
+ end
+end
+
+=begin
+
+some attachment stuff
+rendering_position
+object_type
+attach_num
+attach_method
+
+rendering_position is around (1 << 32) - 1 if its inline
+
+attach_method 1 for plain data?
+attach_method 6 for embedded ole
+
+display_name instead of reading the embedded ole type.
+
+
+PR_RTF_IN_SYNC property is missing or set to FALSE.
+
+
+Before reading from the uncompressed RTF stream, sort the message's attachment
+table on the value of the PR_RENDERING_POSITION property. The attachments will
+now be in order by how they appear in the message.
+
+As your client scans through the RTF stream, check for the token "\objattph".
+The character following the token is the place to put the next attachment from
+the sorted table. Handle attachments that have set their PR_RENDERING_POSITION
+property to -1 separately.
+
+eg from rtf.
+
+\b\f2\fs20{\object\objemb{\*\objclass PBrush}\objw1320\objh1274{\*\objdata
+01050000 <- looks like standard header
+02000000 <- not sure
+07000000 <- this means length of following is 7.
+50427275736800 <- Pbrush\000 in hex
+00000000 <- ?
+00000000 <- ?
+e0570000 <- this is 22496. length of the following in hex
+this is the bitmap data, starting with BM....
+424dde57000000000000360000002800000058000000550000000100180000000000a857000000
+000000000000000000000000000000c8d0d4c8d0d4c8d0d4c8d0d4c8d0d4c8d0d4c8d0d4c8d0d4
+
+---------------
+
+tested 3 different embedded files:
+
+1. excel embedded
+ - "\002OlePres000"[40..-1] can be saved to '.wmf' and opened.
+ - "\002OlePres001" similarly.
+ much better looking image. strange
+ - For the rtf serialization, it has the file contents as an
+ ole, "d0cf11e" serialization, which i can't do yet. this can
+ be extracted as a working .xls
+ followed by a METAFILEPICT chunk, correspoding to one of the
+ ole pres chunks.
+ then the very same metafile chunk in the result bit.
+
+2. pbrush embedded image
+ - "\002OlePres000" wmf as above.
+ - "\001Ole10Native" is a long followed by a plain old .bmp
+ - Serialization:
+ Basic header as before, then bitmap data follows, then the
+ metafile chunk follows, though labeled PBrush again this time.
+ the result chunk was corrupted
+
+3. metafile embedded image
+ - no presentation section, just a
+ - "CONTENTS" section, which can be saved directly as a wmf.
+ different header to the other 2 metafiles. it starts with
+ 9AC6CDD7, which is the Aldus placeable metafile header.
+ (http://wvware.sourceforge.net/caolan/ora-wmf.html)
+ you can decode the left, top, right, bottom, and then
+ multiply by 96, and divide by the metafile unit converter thing
+ to get pixel values.
+
+the above ones were always the plain metafiles
+word filetype (0 = memory, 1 = disk)
+word headersize (always 9)
+word version
+thus leading to the
+0100
+0900
+0003
+pattern i usually see.
+
+=end
+
diff --git a/vendor/ruby-msg/data/mapitags.yaml b/vendor/ruby-msg/data/mapitags.yaml
new file mode 100644
index 000000000..d6c5d5756
--- /dev/null
+++ b/vendor/ruby-msg/data/mapitags.yaml
@@ -0,0 +1,4168 @@
+---
+66b0:
+- PR_RECIPIENT_ON_ASSOC_MSG_COUNT
+- PT_LONG
+3a01:
+- PR_ALTERNATE_RECIPIENT
+- PT_BINARY
+"6628":
+- PR_GW_MTSIN_ENTRYID
+- PT_BINARY
+"0061":
+- PR_END_DATE
+- PT_SYSTIME
+66b1:
+- PR_ATTACH_ON_NORMAL_MSG_COUNT
+- PT_LONG
+"1006":
+- PR_RTF_SYNC_BODY_CRC
+- PT_LONG
+3a02:
+- PR_CALLBACK_TELEPHONE_NUMBER
+- PT_TSTRING
+67aa:
+- PR_ASSOCIATED
+- PT_BOOLEAN
+"6629":
+- PR_GW_MTSOUT_ENTRYID
+- PT_BINARY
+"0062":
+- PR_OWNER_APPT_ID
+- PT_LONG
+fffa:
+- PR_EMS_AB_OBJECT_OID
+- PT_BINARY
+66b2:
+- PR_ATTACH_ON_ASSOC_MSG_COUNT
+- PT_LONG
+36e0:
+- PR_FOLDER_XVIEWINFO_E
+- PT_BINARY
+"1007":
+- PR_RTF_SYNC_BODY_COUNT
+- PT_LONG
+3a03:
+- PR_CONVERSION_PROHIBITED
+- PT_BOOLEAN
+"0063":
+- PR_RESPONSE_REQUESTED
+- PT_BOOLEAN
+fffb:
+- PR_EMS_AB_IS_MASTER
+- PT_BOOLEAN
+002a:
+- PR_RECEIPT_TIME
+- PT_SYSTIME
+66b3:
+- PR_NORMAL_MESSAGE_SIZE
+- PT_LONG|PT_I8
+36e1:
+- PR_FOLDER_VIEWS_ONLY
+- PT_LONG
+"1008":
+- PR_RTF_SYNC_BODY_TAG
+- PT_TSTRING
+3a04:
+- PR_DISCLOSE_RECIPIENTS
+- PT_BOOLEAN
+"0064":
+- PR_SENT_REPRESENTING_ADDRTYPE
+- PT_TSTRING
+fffc:
+- PR_EMS_AB_PARENT_ENTRYID
+- PT_BINARY
+002b:
+- PR_RECIPIENT_REASSIGNMENT_PROHIBITED
+- PT_BOOLEAN
+66b4:
+- PR_ASSOC_MESSAGE_SIZE
+- PT_LONG|PT_I8
+"1009":
+- PR_RTF_COMPRESSED
+- PT_BINARY
+3a05:
+- PR_GENERATION
+- PT_TSTRING
+"0065":
+- PR_SENT_REPRESENTING_EMAIL_ADDRESS
+- PT_TSTRING
+002c:
+- PR_REDIRECTION_HISTORY
+- PT_BINARY
+66b5:
+- PR_FOLDER_PATHNAME
+- PT_TSTRING
+3a06:
+- PR_GIVEN_NAME
+- PT_TSTRING
+fffe:
+- PR_EMS_AB_SERVER
+- PT_TSTRING
+002d:
+- PR_RELATED_IPMS
+- PT_BINARY
+66b6:
+- PR_OWNER_COUNT
+- PT_LONG
+36e4:
+- PR_FREEBUSY_ENTRYIDS
+- PT_MV_BINARY
+3a07:
+- PR_GOVERNMENT_ID_NUMBER
+- PT_TSTRING
+"0066":
+- PR_ORIGINAL_SENDER_ADDRTYPE
+- PT_TSTRING
+002e:
+- PR_ORIGINAL_SENSITIVITY
+- PT_LONG
+"1010":
+- PR_RTF_SYNC_PREFIX_COUNT
+- PT_LONG
+66b7:
+- PR_CONTACT_COUNT
+- PT_LONG
+36e5:
+- PR_DEF_MSG_CLASS
+- PT_UNICODE
+3a08:
+- PR_BUSINESS_TELEPHONE_NUMBER
+- PT_TSTRING
+"0067":
+- PR_ORIGINAL_SENDER_EMAIL_ADDRESS
+- PT_TSTRING
+002f:
+- PR_LANGUAGES
+- PT_TSTRING
+"1011":
+- PR_RTF_SYNC_TRAILING_COUNT
+- PT_LONG
+"6634":
+- PR_CHANGE_ADVISOR
+- PT_OBJECT
+36e6:
+- PR_DEF_FORM_NAME
+- PT_UNICODE
+3a09:
+- PR_HOME_TELEPHONE_NUMBER
+- PT_TSTRING
+0068:
+- PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE
+- PT_TSTRING
+"6635":
+- PR_FAVORITES_DEFAULT_NAME
+- PT_TSTRING
+0069:
+- PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS
+- PT_TSTRING
+"1012":
+- PR_ORIGINALLY_INTENDED_RECIP_ENTRYID
+- PT_BINARY
+0e96:
+- PR_ATTACH_VIRUS_SCAN_INFO
+- PT_BINARY
+"6636":
+- PR_SYS_CONFIG_FOLDER_ENTRYID
+- PT_BINARY
+67f0:
+- PR_PROFILE_SECURE_MAILBOX
+- PT_BINARY
+"1013":
+- PR_BODY_HTML
+- PT_TSTRING
+"3400":
+- PR_DEFAULT_STORE
+- PT_BOOLEAN
+"6637":
+- PR_CHANGE_NOTIFICATION_GUID
+- PT_CLSID
+36e9:
+- PR_GENERATE_EXCHANGE_VIEWS
+- PT_BOOLEAN
+"0070":
+- PR_CONVERSATION_TOPIC
+- PT_TSTRING
+0e5e:
+- PR_MIME_HANDLER_CLASSIDS
+- PT_CLSID
+3a10:
+- PR_ORGANIZATIONAL_ID_NUMBER
+- PT_TSTRING
+"6638":
+- PR_FOLDER_CHILD_COUNT
+- PT_LONG
+"0071":
+- PR_CONVERSATION_INDEX
+- PT_BINARY
+3a11:
+- PR_SURNAME
+- PT_TSTRING
+"0072":
+- PR_ORIGINAL_DISPLAY_BCC
+- PT_TSTRING
+"7001":
+- PR_VD_BINARY
+- PT_BINARY
+3a12:
+- PR_ORIGINAL_ENTRYID
+- PT_BINARY
+"0073":
+- PR_ORIGINAL_DISPLAY_CC
+- PT_TSTRING
+003a:
+- PR_REPORT_NAME
+- PT_TSTRING
+"7002":
+- PR_VD_STRINGS
+- PT_UNICODE
+3a13:
+- PR_ORIGINAL_DISPLAY_NAME
+- PT_TSTRING
+"0074":
+- PR_ORIGINAL_DISPLAY_TO
+- PT_TSTRING
+686b:
+- PR_DELEGATES_SEE_PRIVATE
+- PT_MV_LONG
+003b:
+- PR_SENT_REPRESENTING_SEARCH_KEY
+- PT_BINARY
+66c3:
+- PR_CODE_PAGE_ID
+- PT_LONG
+"7003":
+- PR_VD_FLAGS
+- PT_LONG
+3a14:
+- PR_ORIGINAL_SEARCH_KEY
+- PT_BINARY
+"0075":
+- PR_RECEIVED_BY_ADDRTYPE
+- PT_TSTRING
+686c:
+- PR_PERSONAL_FREEBUSY
+- PT_BINARY
+003c:
+- PR_X400_CONTENT_TYPE
+- PT_BINARY
+66c4:
+- PR_RETENTION_AGE_LIMIT
+- PT_LONG
+"7004":
+- PR_VD_LINK_TO
+- PT_BINARY
+3a15:
+- PR_POSTAL_ADDRESS
+- PT_TSTRING
+"0076":
+- PR_RECEIVED_BY_EMAIL_ADDRESS
+- PT_TSTRING
+686d:
+- PR_PROCESS_MEETING_REQUESTS
+- PT_BOOLEAN
+003d:
+- PR_SUBJECT_PREFIX
+- PT_TSTRING
+66c5:
+- PR_DISABLE_PERUSER_READ
+- PT_BOOLEAN
+"7005":
+- PR_VD_VIEW_FOLDER
+- PT_BINARY
+3a16:
+- PR_COMPANY_NAME
+- PT_TSTRING
+686e:
+- PR_DECLINE_RECURRING_MEETING_REQUESTS
+- PT_BOOLEAN
+003e:
+- PR_NON_RECEIPT_REASON
+- PT_LONG
+66c6:
+- PR_INTERNET_PARSE_STATE
+- PT_BINARY
+"7006":
+- PR_VD_NAME
+- PT_UNICODE
+3a17:
+- PR_TITLE
+- PT_TSTRING
+660a:
+- PR_PROFILE_TYPE
+- PT_LONG
+"0077":
+- PR_RCVD_REPRESENTING_ADDRTYPE
+- PT_TSTRING
+686f:
+- PR_DECLINE_CONFLICTING_MEETING_REQUESTS
+- PT_BOOLEAN
+003f:
+- PR_RECEIVED_BY_ENTRYID
+- PT_BINARY
+66c7:
+- PR_INTERNET_MESSAGE_INFO
+- PT_BINARY
+3a18:
+- PR_DEPARTMENT_NAME
+- PT_TSTRING
+660b:
+- PR_PROFILE_MAILBOX
+- PT_TSTRING
+0078:
+- PR_RCVD_REPRESENTING_EMAIL_ADDRESS
+- PT_TSTRING
+7d01:
+- PR_FAV_AUTOSUBFOLDERS
+- PT_LONG
+"7007":
+- PR_VD_VERSION
+- PT_LONG
+3a19:
+- PR_OFFICE_LOCATION
+- PT_TSTRING
+660c:
+- PR_PROFILE_SERVER
+- PT_TSTRING
+0079:
+- PR_ORIGINAL_AUTHOR_ADDRTYPE
+- PT_TSTRING
+7d02:
+- PR_FAV_PARENT_SOURCE_KEY
+- PT_BINARY
+660d:
+- PR_PROFILE_MAX_RESTRICT
+- PT_LONG
+7d03:
+- PR_FAV_LEVEL_MASK
+- PT_LONG
+"3410":
+- PR_IPM_SUBTREE_SEARCH_KEY
+- PT_BINARY
+660e:
+- PR_PROFILE_AB_FILES_PATH
+- PT_TSTRING
+3a20:
+- PR_TRANSMITTABLE_DISPLAY_NAME
+- PT_TSTRING
+"3411":
+- PR_IPM_OUTBOX_SEARCH_KEY
+- PT_BINARY
+"6779":
+- PR_PF_QUOTA_STYLE
+- PT_LONG
+0c0a:
+- PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY
+- PT_BOOLEAN
+660f:
+- PR_PROFILE_FAVFLD_DISPLAY_NAME
+- PT_TSTRING
+3a21:
+- PR_PAGER_TELEPHONE_NUMBER
+- PT_TSTRING
+"3412":
+- PR_IPM_WASTEBASKET_SEARCH_KEY
+- PT_BINARY
+0c0b:
+- PR_PHYSICAL_DELIVERY_MODE
+- PT_LONG
+3a22:
+- PR_USER_CERTIFICATE
+- PT_BINARY
+"3413":
+- PR_IPM_SENTMAIL_SEARCH_KEY
+- PT_BINARY
+0c0c:
+- PR_PHYSICAL_DELIVERY_REPORT_REQUEST
+- PT_LONG
+7d07:
+- PR_FAV_INHERIT_AUTO
+- PT_LONG
+65a0:
+- PR_RULE_SERVER_RULE_ID
+- PT_I8
+3a23:
+- PR_PRIMARY_FAX_NUMBER
+- PT_TSTRING
+"3414":
+- PR_MDB_PROVIDER
+- PT_BINARY
+0c0d:
+- PR_PHYSICAL_FORWARDING_ADDRESS
+- PT_BINARY
+004a:
+- PR_DISC_VAL
+- PT_BOOLEAN
+7d08:
+- PR_FAV_DEL_SUBS
+- PT_BINARY
+"6650":
+- PR_RULE_ACTION_NUMBER
+- PT_LONG
+3a24:
+- PR_BUSINESS_FAX_NUMBER
+- PT_TSTRING
+"3415":
+- PR_RECEIVE_FOLDER_SETTINGS
+- PT_OBJECT
+0c0e:
+- PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED
+- PT_BOOLEAN
+004b:
+- PR_ORIG_MESSAGE_CLASS
+- PT_TSTRING
+"6651":
+- PR_RULE_FOLDER_ENTRYID
+- PT_BINARY
+"6783":
+- PR_SEARCH_FLAGS
+- PT_LONG
+3a25:
+- PR_HOME_FAX_NUMBER
+- PT_TSTRING
+674a:
+- PR_MID
+- PT_I8
+0c0f:
+- PR_PHYSICAL_FORWARDING_PROHIBITED
+- PT_BOOLEAN
+004c:
+- PR_ORIGINAL_AUTHOR_ENTRYID
+- PT_BINARY
+"6652":
+- PR_ACTIVE_USER_ENTRYID
+- PT_BINARY
+3a26:
+- PR_COUNTRY
+- PT_TSTRING
+674b:
+- PR_CATEG_ID
+- PT_I8
+004d:
+- PR_ORIGINAL_AUTHOR_NAME
+- PT_TSTRING
+"1030":
+- PR_INTERNET_APPROVED
+- PT_TSTRING
+"6653":
+- PR_X400_ENVELOPE_TYPE
+- PT_LONG
+3a27:
+- PR_LOCALITY
+- PT_TSTRING
+661a:
+- PR_USER_NAME
+- PT_TSTRING
+674c:
+- PR_PARENT_CATEG_ID
+- PT_I8
+004e:
+- PR_ORIGINAL_SUBMIT_TIME
+- PT_SYSTIME
+"1031":
+- PR_INTERNET_CONTROL
+- PT_TSTRING
+"6654":
+- PR_MSG_FOLD_TIME
+- PT_SYSTIME
+3a28:
+- PR_STATE_OR_PROVINCE
+- PT_TSTRING
+661b:
+- PR_MAILBOX_OWNER_ENTRYID
+- PT_BINARY
+674d:
+- PR_INST_ID
+- PT_I8
+004f:
+- PR_REPLY_RECIPIENT_ENTRIES
+- PT_BINARY
+"1032":
+- PR_INTERNET_DISTRIBUTION
+- PT_TSTRING
+3a29:
+- PR_STREET_ADDRESS
+- PT_TSTRING
+661c:
+- PR_MAILBOX_OWNER_NAME
+- PT_TSTRING
+674e:
+- PR_INSTANCE_NUM
+- PT_LONG
+"1033":
+- PR_INTERNET_FOLLOWUP_TO
+- PT_TSTRING
+"6655":
+- PR_ICS_CHANGE_KEY
+- PT_BINARY
+661d:
+- PR_OOF_STATE
+- PT_BOOLEAN
+674f:
+- PR_ADDRBOOK_MID
+- PT_I8
+661e:
+- PR_SCHEDULE_FOLDER_ENTRYID
+- PT_BINARY
+"1034":
+- PR_INTERNET_LINES
+- PT_LONG
+3a30:
+- PR_ASSISTANT
+- PT_TSTRING
+0c1a:
+- PR_SENDER_NAME
+- PT_TSTRING
+661f:
+- PR_IPM_DAF_ENTRYID
+- PT_BINARY
+"1035":
+- PR_INTERNET_MESSAGE_ID
+- PT_TSTRING
+"6658":
+- PR_GW_ADMIN_OPERATIONS
+- PT_LONG
+0c1b:
+- PR_SUPPLEMENTARY_INFO
+- PT_TSTRING
+"1036":
+- PR_INTERNET_NEWSGROUPS
+- PT_TSTRING
+"6659":
+- PR_INTERNET_CONTENT
+- PT_BINARY
+0c1c:
+- PR_TYPE_OF_MTS_USER
+- PT_LONG
+0c1d:
+- PR_SENDER_SEARCH_KEY
+- PT_BINARY
+005a:
+- PR_ORIGINAL_SENDER_NAME
+- PT_TSTRING
+"1037":
+- PR_INTERNET_ORGANIZATION
+- PT_TSTRING
+66aa:
+- PR_RESTRICTION_COUNT
+- PT_LONG
+0c1e:
+- PR_SENDER_ADDRTYPE
+- PT_TSTRING
+005b:
+- PR_ORIGINAL_SENDER_ENTRYID
+- PT_BINARY
+10c0:
+- PR_SMTP_TEMP_TBL_DATA
+- PT_BINARY
+"6660":
+- PR_TRACE_INFO
+- PT_BINARY
+"1038":
+- PR_INTERNET_NNTP_PATH
+- PT_TSTRING
+66ab:
+- PR_CATEG_COUNT
+- PT_LONG
+675a:
+- PR_PCL_EXPORT
+- PT_BINARY
+0c1f:
+- PR_SENDER_EMAIL_ADDRESS
+- PT_TSTRING
+005c:
+- PR_ORIGINAL_SENDER_SEARCH_KEY
+- PT_BINARY
+10c1:
+- PR_SMTP_TEMP_TBL_DATA_2
+- PT_LONG
+35e0:
+- PR_IPM_SUBTREE_ENTRYID
+- PT_BINARY
+"6661":
+- PR_SUBJECT_TRACE_INFO
+- PT_BINARY
+"1039":
+- PR_INTERNET_REFERENCES
+- PT_TSTRING
+66ac:
+- PR_CACHED_COLUMN_COUNT
+- PT_LONG
+675b:
+- PR_CN_MV_EXPORT
+- PT_MV_BINARY
+"8100":
+- PR_EMS_AB_OPEN_RETRY_INTERVAL
+- PT_LONG
+005d:
+- PR_ORIGINAL_SENT_REPRESENTING_NAME
+- PT_TSTRING
+10c2:
+- PR_SMTP_TEMP_TBL_DATA_3
+- PT_BINARY
+"6662":
+- PR_RECIPIENT_NUMBER
+- PT_LONG
+66ad:
+- PR_NORMAL_MSG_W_ATTACH_COUNT
+- PT_LONG
+662a:
+- PR_TRANSFER_ENABLED
+- PT_BOOLEAN
+"8101":
+- PR_EMS_AB_ORGANIZATION_NAME
+- PT_MV_TSTRING
+005e:
+- PR_ORIGINAL_SENT_REPRESENTING_ENTRYID
+- PT_BINARY
+10c3:
+- PR_CAL_START_TIME
+- PT_SYSTIME
+"1040":
+- PR_NNTP_XREF
+- PT_TSTRING
+35e2:
+- PR_IPM_OUTBOX_ENTRYID
+- PT_BINARY
+"6663":
+- PR_MTS_SUBJECT_ID
+- PT_BINARY
+66ae:
+- PR_ASSOC_MSG_W_ATTACH_COUNT
+- PT_LONG
+662b:
+- PR_TEST_LINE_SPEED
+- PT_BINARY
+"8102":
+- PR_EMS_AB_ORGANIZATIONAL_UNIT_NAME
+- PT_MV_TSTRING
+005f:
+- PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY
+- PT_BINARY
+10c4:
+- PR_CAL_END_TIME
+- PT_SYSTIME
+"1041":
+- PR_INTERNET_PRECEDENCE
+- PT_TSTRING
+35e3:
+- PR_IPM_WASTEBASKET_ENTRYID
+- PT_BINARY
+"6664":
+- PR_REPORT_DESTINATION_NAME
+- PT_TSTRING
+66af:
+- PR_RECIPIENT_ON_NORMAL_MSG_COUNT
+- PT_LONG
+662c:
+- PR_HIERARCHY_SYNCHRONIZER
+- PT_OBJECT
+"8103":
+- PR_EMS_AB_ORIGINAL_DISPLAY_TABLE
+- PT_BINARY
+10c5:
+- PR_CAL_RECURRING_ID
+- PT_SYSTIME
+"1042":
+- PR_IN_REPLY_TO_ID
+- PT_UNICODE
+35e4:
+- PR_IPM_SENTMAIL_ENTRYID
+- PT_BINARY
+"6665":
+- PR_REPORT_DESTINATION_ENTRYID
+- PT_BINARY
+662d:
+- PR_CONTENTS_SYNCHRONIZER
+- PT_OBJECT
+"8104":
+- PR_EMS_AB_ORIGINAL_DISPLAY_TABLE_MSDOS
+- PT_BINARY
+10c6:
+- PR_DAV_SUBMIT_DATA
+- PT_UNICODE
+"1043":
+- PR_LIST_HELP
+- PT_UNICODE
+35e5:
+- PR_VIEWS_ENTRYID
+- PT_BINARY
+662e:
+- PR_COLLECTOR
+- PT_OBJECT
+36df:
+- PR_FOLDER_WEBVIEWINFO
+- PT_BINARY
+"8105":
+- PR_EMS_AB_OUTBOUND_SITES
+- PT_OBJECT|PT_MV_TSTRING
+10c7:
+- PR_CDO_EXPANSION_INDEX
+- PT_LONG
+"1044":
+- PR_LIST_SUBSCRIBE
+- PT_UNICODE
+35e6:
+- PR_COMMON_VIEWS_ENTRYID
+- PT_BINARY
+"6666":
+- PR_CONTENT_SEARCH_KEY
+- PT_BINARY
+662f:
+- PR_FAST_TRANSFER
+- PT_OBJECT
+"8106":
+- PR_EMS_AB_P_SELECTOR
+- PT_BINARY
+10c8:
+- PR_IFS_INTERNAL_DATA
+- PT_BINARY
+3a40:
+- PR_SEND_RICH_INFO
+- PT_BOOLEAN
+35e7:
+- PR_FINDER_ENTRYID
+- PT_BINARY
+"6667":
+- PR_FOREIGN_ID
+- PT_BINARY
+"1045":
+- PR_LIST_UNSUBSCRIBE
+- PT_UNICODE
+3a41:
+- PR_WEDDING_ANNIVERSARY
+- PT_SYSTIME
+"6668":
+- PR_FOREIGN_REPORT_ID
+- PT_BINARY
+"8107":
+- PR_EMS_AB_P_SELECTOR_INBOUND
+- PT_BINARY
+"3301":
+- PR_FORM_VERSION
+- PT_TSTRING
+3a42:
+- PR_BIRTHDAY
+- PT_SYSTIME
+"6669":
+- PR_FOREIGN_SUBJECT_ID
+- PT_BINARY
+3a0a:
+- PR_INITIALS
+- PT_TSTRING
+"8108":
+- PR_EMS_AB_PER_MSG_DIALOG_DISPLAY_TABLE
+- PT_BINARY
+"3302":
+- PR_FORM_CLSID
+- PT_CLSID
+3a43:
+- PR_HOBBIES
+- PT_TSTRING
+"8109":
+- PR_EMS_AB_PER_RECIP_DIALOG_DISPLAY_TABLE
+- PT_BINARY
+"6670":
+- PR_LONGTERM_ENTRYID_FROM_TABLE
+- PT_BINARY
+"3303":
+- PR_FORM_CONTACT_NAME
+- PT_TSTRING
+3a44:
+- PR_MIDDLE_NAME
+- PT_TSTRING
+3a0b:
+- PR_KEYWORD
+- PT_TSTRING
+65c2:
+- PR_REPLY_TEMPLATE_ID
+- PT_BINARY
+"6671":
+- PR_MEMBER_ID
+- PT_I8
+"3304":
+- PR_FORM_CATEGORY
+- PT_TSTRING
+3a45:
+- PR_DISPLAY_NAME_PREFIX
+- PT_TSTRING
+3a0c:
+- PR_LANGUAGE
+- PT_TSTRING
+"6672":
+- PR_MEMBER_NAME
+- PT_TSTRING
+"3305":
+- PR_FORM_CATEGORY_SUB
+- PT_TSTRING
+3a46:
+- PR_PROFESSION
+- PT_TSTRING
+"8110":
+- PR_EMS_AB_PUBLIC_DELEGATES_BL
+- PT_OBJECT|PT_MV_TSTRING
+3a0d:
+- PR_LOCATION
+- PT_TSTRING
+"6673":
+- PR_MEMBER_RIGHTS
+- PT_LONG
+"3306":
+- PR_FORM_HOST_MAP
+- PT_MV_LONG
+3a47:
+- PR_PREFERRED_BY_NAME
+- PT_TSTRING
+663a:
+- PR_HAS_RULES
+- PT_BOOLEAN
+"8111":
+- PR_EMS_AB_QUOTA_NOTIFICATION_SCHEDULE
+- PT_BINARY
+3a0e:
+- PR_MAIL_PERMISSION
+- PT_BOOLEAN
+"6674":
+- PR_RULE_ID
+- PT_I8
+"3307":
+- PR_FORM_HIDDEN
+- PT_BOOLEAN
+3a48:
+- PR_SPOUSE_NAME
+- PT_TSTRING
+663b:
+- PR_ADDRESS_BOOK_ENTRYID
+- PT_BINARY
+36ec:
+- PR_AGING_PERIOD
+- PT_LONG
+"8112":
+- PR_EMS_AB_QUOTA_NOTIFICATION_STYLE
+- PT_LONG
+3a0f:
+- PR_MHS_COMMON_NAME
+- PT_TSTRING
+"6675":
+- PR_RULE_IDS
+- PT_BINARY
+"3308":
+- PR_FORM_DESIGNER_NAME
+- PT_TSTRING
+3a49:
+- PR_COMPUTER_NETWORK_NAME
+- PT_TSTRING
+663c:
+- PR_PUBLIC_FOLDER_ENTRYID
+- PT_BINARY
+"8113":
+- PR_EMS_AB_RANGE_LOWER
+- PT_LONG
+"6676":
+- PR_RULE_SEQUENCE
+- PT_LONG
+"3309":
+- PR_FORM_DESIGNER_GUID
+- PT_CLSID
+663d:
+- PR_OFFLINE_FLAGS
+- PT_LONG
+36ee:
+- PR_AGING_GRANULARITY
+- PT_LONG
+7c00:
+- PR_FAV_DISPLAY_NAME
+- PT_TSTRING
+"8114":
+- PR_EMS_AB_RANGE_UPPER
+- PT_LONG
+663e:
+- PR_HIERARCHY_CHANGE_NUM
+- PT_LONG
+"8115":
+- PR_EMS_AB_RAS_CALLBACK_NUMBER
+- PT_TSTRING
+3a50:
+- PR_PERSONAL_HOME_PAGE
+- PT_TSTRING
+"6677":
+- PR_RULE_STATE
+- PT_LONG
+663f:
+- PR_HAS_MODERATOR_RULES
+- PT_BOOLEAN
+7c02:
+- PR_FAV_PUBLIC_SOURCE_KEY
+- PT_BINARY
+"8116":
+- PR_EMS_AB_RAS_PHONE_NUMBER
+- PT_TSTRING
+3a51:
+- PR_BUSINESS_HOME_PAGE
+- PT_TSTRING
+"6678":
+- PR_RULE_USER_FLAGS
+- PT_LONG
+"8117":
+- PR_EMS_AB_RAS_PHONEBOOK_ENTRY_NAME
+- PT_TSTRING
+3a52:
+- PR_CONTACT_VERSION
+- PT_CLSID
+"6679":
+- PR_RULE_CONDITION
+- PT_SRESTRICTION
+3a1a:
+- PR_PRIMARY_TELEPHONE_NUMBER
+- PT_TSTRING
+7c04:
+- PR_OST_OSTID
+- PT_BINARY
+"4000":
+- PR_NEW_ATTACH
+- PT_LONG
+3a53:
+- PR_CONTACT_ENTRYIDS
+- PT_MV_BINARY
+3a1b:
+- PR_BUSINESS2_TELEPHONE_NUMBER
+- PT_TSTRING
+007a:
+- PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS
+- PT_TSTRING
+"8118":
+- PR_EMS_AB_RAS_REMOTE_SRVR_NAME
+- PT_TSTRING
+"4001":
+- PR_START_EMBED
+- PT_LONG
+3a54:
+- PR_CONTACT_ADDRTYPES
+- PT_MV_TSTRING
+81a1:
+- PR_EMS_AB_X500_RDN
+- PT_TSTRING
+340d:
+- PR_STORE_SUPPORT_MASK
+- PT_LONG
+007b:
+- PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE
+- PT_TSTRING
+"8119":
+- PR_EMS_AB_REGISTERED_ADDRESS
+- PT_MV_BINARY
+"4002":
+- PR_END_EMBED
+- PT_LONG
+"6681":
+- PR_RULE_PROVIDER
+- PT_TSTRING
+3a55:
+- PR_CONTACT_DEFAULT_ADDRESS_INDEX
+- PT_LONG
+81a2:
+- PR_EMS_AB_X500_NC
+- PT_TSTRING
+3a1c:
+- PR_MOBILE_TELEPHONE_NUMBER
+- PT_TSTRING
+340e:
+- PR_STORE_STATE
+- PT_LONG
+007c:
+- PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS
+- PT_TSTRING
+"4003":
+- PR_START_RECIP
+- PT_LONG
+"6682":
+- PR_RULE_NAME
+- PT_TSTRING
+3a56:
+- PR_CONTACT_EMAIL_ADDRESSES
+- PT_MV_TSTRING
+677b:
+- PR_PF_STORAGE_QUOTA
+- PT_LONG
+81a3:
+- PR_EMS_AB_REFERRAL_LIST
+- PT_MV_TSTRING
+"8120":
+- PR_EMS_AB_REPORT_TO_ORIGINATOR
+- PT_BOOLEAN
+3a1d:
+- PR_RADIO_TELEPHONE_NUMBER
+- PT_TSTRING
+007d:
+- PR_TRANSPORT_MESSAGE_HEADERS
+- PT_TSTRING
+"6683":
+- PR_RULE_LEVEL
+- PT_LONG
+3a57:
+- PR_COMPANY_MAIN_PHONE_NUMBER
+- PT_TSTRING
+664a:
+- PR_HAS_NAMED_PROPERTIES
+- PT_BOOLEAN
+81a4:
+- PR_EMS_AB_NNTP_DISTRIBUTIONS_FLAG
+- PT_BOOLEAN
+"8121":
+- PR_EMS_AB_REPORT_TO_OWNER
+- PT_BOOLEAN
+3a1e:
+- PR_CAR_TELEPHONE_NUMBER
+- PT_TSTRING
+007e:
+- PR_DELEGATION
+- PT_BINARY
+"4004":
+- PR_END_RECIP
+- PT_LONG
+"6684":
+- PR_RULE_PROVIDER_DATA
+- PT_BINARY
+3a58:
+- PR_CHILDRENS_NAMES
+- PT_MV_TSTRING
+664b:
+- PR_REPLICA_VERSION
+- PT_I8
+81a5:
+- PR_EMS_AB_ASSOC_PROTOCOL_CFG_NNTP
+- PT_OBJECT|PT_MV_TSTRING
+"8122":
+- PR_EMS_AB_REQ_SEQ
+- PT_LONG
+3a1f:
+- PR_OTHER_TELEPHONE_NUMBER
+- PT_TSTRING
+007f:
+- PR_TNEF_CORRELATION_KEY
+- PT_BINARY
+"4005":
+- PR_END_CC_RECIP
+- PT_LONG
+"6685":
+- PR_LAST_FULL_BACKUP
+- PT_SYSTIME
+3a59:
+- PR_HOME_ADDRESS_CITY
+- PT_TSTRING
+81a6:
+- PR_EMS_AB_NNTP_NEWSFEEDS
+- PT_OBJECT|PT_MV_TSTRING
+"8123":
+- PR_EMS_AB_RESPONSIBLE_LOCAL_DXA
+- PT_OBJECT|PT_MV_TSTRING
+"4006":
+- PR_END_BCC_RECIP
+- PT_LONG
+"8124":
+- PR_EMS_AB_RID_SERVER
+- PT_OBJECT|PT_MV_TSTRING
+"4007":
+- PR_END_P1_RECIP
+- PT_LONG
+"6687":
+- PR_PROFILE_ADDR_INFO
+- PT_BINARY
+81a8:
+- PR_EMS_AB_ENABLED_PROTOCOL_CFG
+- PT_BOOLEAN
+"8125":
+- PR_EMS_AB_ROLE_OCCUPANT
+- PT_OBJECT|PT_MV_TSTRING
+81a9:
+- PR_EMS_AB_HTTP_PUB_AB_ATTRIBUTES
+- PT_MV_TSTRING
+"8126":
+- PR_EMS_AB_ROUTING_LIST
+- PT_MV_TSTRING
+"4009":
+- PR_START_TOP_FLD
+- PT_LONG
+3a60:
+- PR_OTHER_ADDRESS_COUNTRY
+- PT_TSTRING
+"8127":
+- PR_EMS_AB_RTS_CHECKPOINT_SIZE
+- PT_LONG
+"5902":
+- PR_INET_MAIL_OVERRIDE_FORMAT
+- PT_LONG
+3a61:
+- PR_OTHER_ADDRESS_POSTAL_CODE
+- PT_TSTRING
+"6689":
+- PR_PROFILE_OPTIONS_DATA
+- PT_BINARY
+3a2a:
+- PR_POSTAL_CODE
+- PT_TSTRING
+"8128":
+- PR_EMS_AB_RTS_RECOVERY_TIMEOUT
+- PT_LONG
+"4010":
+- PR_START_FAI_MSG
+- PT_LONG
+3a62:
+- PR_OTHER_ADDRESS_STATE_OR_PROVINCE
+- PT_TSTRING
+81b0:
+- PR_EMS_AB_OUTBOUND_HOST_TYPE
+- PT_BOOLEAN
+3a2b:
+- PR_POST_BOX
+- PT_TSTRING
+"4011":
+- PR_NEW_FX_FOLDER
+- PT_BINARY
+7ffa:
+- PR_ATTACHMENT_LINKID
+- PT_LONG
+65e0:
+- PR_SOURCE_KEY
+- PT_BINARY
+3a63:
+- PR_OTHER_ADDRESS_STREET
+- PT_TSTRING
+81b1:
+- PR_EMS_AB_PROXY_GENERATION_ENABLED
+- PT_BOOLEAN
+3a2c:
+- PR_TELEX_NUMBER
+- PT_TSTRING
+"8129":
+- PR_EMS_AB_RTS_WINDOW_SIZE
+- PT_LONG
+"4012":
+- PR_INCR_SYNC_CHG
+- PT_LONG
+7ffb:
+- PR_EXCEPTION_STARTTIME
+- PT_SYSTIME
+65e1:
+- PR_PARENT_SOURCE_KEY
+- PT_BINARY
+"6690":
+- PR_REPLICATION_STYLE
+- PT_LONG
+3a64:
+- PR_OTHER_ADDRESS_POST_OFFICE_BOX
+- PT_TSTRING
+81b2:
+- PR_EMS_AB_ROOT_NEWSGROUPS_FOLDER_ID
+- PT_BINARY
+10f1:
+- PR_OWA_URL
+- PT_TSTRING
+"4013":
+- PR_INCR_SYNC_DEL
+- PT_LONG
+7ffc:
+- PR_EXCEPTION_ENDTIME
+- PT_SYSTIME
+65e2:
+- PR_CHANGE_KEY
+- PT_BINARY
+"6691":
+- PR_REPLICATION_SCHEDULE
+- PT_BINARY
+81b3:
+- PR_EMS_AB_CONNECTION_TYPE
+- PT_BOOLEAN
+3a2d:
+- PR_ISDN_NUMBER
+- PT_TSTRING
+"8130":
+- PR_EMS_AB_SERIAL_NUMBER
+- PT_MV_TSTRING
+10f2:
+- PR_DISABLE_FULL_FIDELITY
+- PT_BOOLEAN
+"4014":
+- PR_INCR_SYNC_END
+- PT_LONG
+7ffd:
+- PR_ATTACHMENT_FLAGS
+- PT_LONG
+65e3:
+- PR_PREDECESSOR_CHANGE_LIST
+- PT_BINARY
+"6692":
+- PR_REPLICATION_MESSAGE_PRIORITY
+- PT_LONG
+81b4:
+- PR_EMS_AB_CONNECTION_LIST_FILTER_TYPE
+- PT_LONG
+"8131":
+- PR_EMS_AB_SERVICE_ACTION_FIRST
+- PT_LONG
+3a2e:
+- PR_ASSISTANT_TELEPHONE_NUMBER
+- PT_TSTRING
+10f3:
+- PR_URL_COMP_NAME
+- PT_UNICODE
+7ffe:
+- PR_ATTACHMENT_HIDDEN
+- PT_BOOLEAN
+65e4:
+- PR_SYNCHRONIZE_FLAGS
+- PT_LONG
+"6693":
+- PR_OVERALL_MSG_AGE_LIMIT
+- PT_LONG
+665a:
+- PR_HAS_ATTACH_FROM_IMAIL
+- PT_BOOLEAN
+81b5:
+- PR_EMS_AB_PORT_NUMBER
+- PT_LONG
+"8132":
+- PR_EMS_AB_SERVICE_ACTION_OTHER
+- PT_LONG
+3a2f:
+- PR_HOME2_TELEPHONE_NUMBER
+- PT_TSTRING
+10f4:
+- PR_ATTR_HIDDEN
+- PT_BOOLEAN
+"4015":
+- PR_INCR_SYNC_MSG
+- PT_LONG
+65e5:
+- PR_AUTO_ADD_NEW_SUBS
+- PT_BOOLEAN
+"6694":
+- PR_REPLICATION_ALWAYS_INTERVAL
+- PT_LONG
+"5909":
+- PR_MSG_EDITOR_FORMAT
+- PT_LONG
+665b:
+- PR_ORIGINATOR_NAME
+- PT_TSTRING
+"8001":
+- PR_EMS_AB_DISPLAY_NAME_OVERRIDE
+- PT_BOOLEAN
+81b6:
+- PR_EMS_AB_PROTOCOL_SETTINGS
+- PT_MV_TSTRING
+"8133":
+- PR_EMS_AB_SERVICE_ACTION_SECOND
+- PT_LONG
+10f5:
+- PR_ATTR_SYSTEM
+- PT_BOOLEAN
+"4016":
+- PR_FX_DEL_PROP
+- PT_LONG
+65e6:
+- PR_NEW_SUBS_GET_AUTO_ADD
+- PT_BOOLEAN
+"6695":
+- PR_REPLICATION_MSG_SIZE
+- PT_LONG
+665c:
+- PR_ORIGINATOR_ADDR
+- PT_TSTRING
+81b7:
+- PR_EMS_AB_GROUP_BY_ATTR_1
+- PT_TSTRING
+"8134":
+- PR_EMS_AB_SERVICE_RESTART_DELAY
+- PT_LONG
+10f6:
+- PR_ATTR_READONLY
+- PT_BOOLEAN
+"4017":
+- PR_IDSET_GIVEN
+- PT_LONG
+65e7:
+- PR_MESSAGE_SITE_NAME
+- PT_TSTRING
+"6696":
+- PR_IS_NEWSGROUP_ANCHOR
+- PT_BOOLEAN
+103a:
+- PR_SUPERSEDES
+- PT_TSTRING
+665d:
+- PR_ORIGINATOR_ADDRTYPE
+- PT_TSTRING
+"8003":
+- PR_EMS_AB_CA_CERTIFICATE
+- PT_MV_BINARY
+81b8:
+- PR_EMS_AB_GROUP_BY_ATTR_2
+- PT_TSTRING
+"8135":
+- PR_EMS_AB_SERVICE_RESTART_MESSAGE
+- PT_TSTRING
+65e8:
+- PR_MESSAGE_PROCESSED
+- PT_BOOLEAN
+"6697":
+- PR_IS_NEWSGROUP
+- PT_BOOLEAN
+103b:
+- PR_POST_FOLDER_ENTRIES
+- PT_BINARY
+665e:
+- PR_ORIGINATOR_ENTRYID
+- PT_BINARY
+"8004":
+- PR_EMS_AB_FOLDER_PATHNAME
+- PT_TSTRING
+81b9:
+- PR_EMS_AB_GROUP_BY_ATTR_3
+- PT_TSTRING
+"8136":
+- PR_EMS_AB_SESSION_DISCONNECT_TIMER
+- PT_LONG
+"4019":
+- PR_SENDER_FLAGS
+- PT_LONG
+65e9:
+- PR_RULE_MSG_STATE
+- PT_LONG
+3a70:
+- PR_USER_X509_CERTIFICATE
+- PT_MV_BINARY
+"6698":
+- PR_REPLICA_LIST
+- PT_BINARY
+103c:
+- PR_POST_FOLDER_NAMES
+- PT_TSTRING
+665f:
+- PR_ARRIVAL_TIME
+- PT_SYSTIME
+"8005":
+- PR_EMS_AB_MANAGER
+- PT_OBJECT|PT_MV_TSTRING
+"8137":
+- PR_EMS_AB_SITE_AFFINITY
+- PT_MV_TSTRING
+3a71:
+- PR_SEND_INTERNET_ENCODING
+- PT_LONG
+103d:
+- PR_POST_REPLY_FOLDER_ENTRIES
+- PT_BINARY
+35df:
+- PR_VALID_FOLDER_MASK
+- PT_LONG
+"8006":
+- PR_EMS_AB_HOME_MDB
+- PT_OBJECT|PT_MV_TSTRING
+"8138":
+- PR_EMS_AB_SITE_PROXY_SPACE
+- PT_MV_TSTRING
+"4020":
+- PR_READ_RECEIPT_FLAGS
+- PT_LONG
+"6699":
+- PR_OVERALL_AGE_LIMIT
+- PT_LONG
+103e:
+- PR_POST_REPLY_FOLDER_NAMES
+- PT_TSTRING
+"8007":
+- PR_EMS_AB_HOME_MTA
+- PT_OBJECT|PT_MV_TSTRING
+"8139":
+- PR_EMS_AB_SPACE_LAST_COMPUTED
+- PT_SYSTIME
+"4021":
+- PR_SOFT_DELETES
+- PT_BOOLEAN
+65f0:
+- PR_RULE_MSG_CONDITION
+- PT_BINARY
+103f:
+- PR_POST_REPLY_DENIED
+- PT_BINARY
+81c0:
+- PR_EMS_AB_VIEW_CONTAINER_2
+- PT_TSTRING
+65f1:
+- PR_RULE_MSG_CONDITION_LCID
+- PT_LONG
+81c1:
+- PR_EMS_AB_VIEW_CONTAINER_3
+- PT_TSTRING
+65f2:
+- PR_RULE_MSG_VERSION
+- PT_SHORT
+81c2:
+- PR_EMS_AB_PROMO_EXPIRATION
+- PT_SYSTIME
+"8008":
+- PR_EMS_AB_IS_MEMBER_OF_DL
+- PT_OBJECT|PT_MV_TSTRING
+65f3:
+- PR_RULE_MSG_SEQUENCE
+- PT_LONG
+10ca:
+- PR_CAL_REMINDER_NEXT_TIME
+- PT_SYSTIME
+81c3:
+- PR_EMS_AB_DISABLED_GATEWAY_PROXY
+- PT_MV_TSTRING
+"8140":
+- PR_EMS_AB_T_SELECTOR
+- PT_BINARY
+"8009":
+- PR_EMS_AB_MEMBER
+- PT_OBJECT|PT_MV_TSTRING
+"1080":
+- PR_ACTION
+- PT_LONG
+65f4:
+- PR_PREVENT_MSG_CREATE
+- PT_BOOLEAN
+81c4:
+- PR_EMS_AB_COMPROMISED_KEY_LIST
+- PT_BINARY
+"8141":
+- PR_EMS_AB_T_SELECTOR_INBOUND
+- PT_BINARY
+"1081":
+- PR_ACTION_FLAG
+- PT_LONG
+65f5:
+- PR_IMAP_INTERNAL_DATE
+- PT_SYSTIME
+"8010":
+- PR_EMS_AB_HELP_DATA32
+- PT_BINARY
+81c5:
+- PR_EMS_AB_INSADMIN
+- PT_OBJECT|PT_MV_TSTRING
+"8142":
+- PR_EMS_AB_TARGET_MTAS
+- PT_MV_TSTRING
+"1082":
+- PR_ACTION_DATE
+- PT_SYSTIME
+666c:
+- PR_IN_CONFLICT
+- PT_BOOLEAN
+"8011":
+- PR_EMS_AB_TARGET_ADDRESS
+- PT_TSTRING
+81c6:
+- PR_EMS_AB_OVERRIDE_NNTP_CONTENT_FORMAT
+- PT_BOOLEAN
+"8143":
+- PR_EMS_AB_TELETEX_TERMINAL_IDENTIFIER
+- PT_MV_BINARY
+3f00:
+- PR_CONTROL_FLAGS
+- PT_LONG
+810a:
+- PR_EMS_AB_PERIOD_REP_SYNC_TIMES
+- PT_BINARY
+"8012":
+- PR_EMS_AB_TELEPHONE_NUMBER
+- PT_MV_TSTRING
+81c7:
+- PR_EMS_AB_OBJ_VIEW_CONTAINERS
+- PT_OBJECT|PT_MV_TSTRING
+"8144":
+- PR_EMS_AB_TEMP_ASSOC_THRESHOLD
+- PT_LONG
+3f01:
+- PR_CONTROL_STRUCTURE
+- PT_BINARY
+810b:
+- PR_EMS_AB_PERIOD_REPL_STAGGER
+- PT_LONG
+"8013":
+- PR_EMS_AB_NT_SECURITY_DESCRIPTOR
+- PT_BINARY
+"8145":
+- PR_EMS_AB_TOMBSTONE_LIFETIME
+- PT_LONG
+3f02:
+- PR_CONTROL_TYPE
+- PT_LONG
+810c:
+- PR_EMS_AB_POSTAL_ADDRESS
+- PT_MV_BINARY
+"8014":
+- PR_EMS_AB_HOME_MDB_BL
+- PT_OBJECT|PT_MV_TSTRING
+"8146":
+- PR_EMS_AB_TRACKING_LOG_PATH_NAME
+- PT_TSTRING
+3f03:
+- PR_DELTAX
+- PT_LONG
+810d:
+- PR_EMS_AB_PREFERRED_DELIVERY_METHOD
+- PT_MV_LONG
+"8015":
+- PR_EMS_AB_PUBLIC_DELEGATES
+- PT_OBJECT|PT_MV_TSTRING
+"8147":
+- PR_EMS_AB_TRANS_RETRY_MINS
+- PT_LONG
+3f04:
+- PR_DELTAY
+- PT_LONG
+810e:
+- PR_EMS_AB_PRMD
+- PT_TSTRING
+3a4a:
+- PR_CUSTOMER_ID
+- PT_TSTRING
+"8016":
+- PR_EMS_AB_CERTIFICATE_REVOCATION_LIST
+- PT_BINARY
+"8148":
+- PR_EMS_AB_TRANS_TIMEOUT_MINS
+- PT_LONG
+"4030":
+- PR_SENDER_SIMPLE_DISP_NAME
+- PT_UNICODE
+3f05:
+- PR_XPOS
+- PT_LONG
+810f:
+- PR_EMS_AB_PROXY_GENERATOR_DLL
+- PT_TSTRING
+330a:
+- PR_FORM_MESSAGE_BEHAVIOR
+- PT_LONG
+3a4b:
+- PR_TTYTDD_PHONE_NUMBER
+- PT_TSTRING
+"8017":
+- PR_EMS_AB_ADDRESS_ENTRY_DISPLAY_TABLE
+- PT_BINARY
+"8149":
+- PR_EMS_AB_TRANSFER_RETRY_INTERVAL
+- PT_LONG
+"4031":
+- PR_SENT_REPRESENTING_SIMPLE_DISP_NAME
+- PT_UNICODE
+3f06:
+- PR_YPOS
+- PT_LONG
+3a4c:
+- PR_FTP_SITE
+- PT_TSTRING
+"8018":
+- PR_EMS_AB_ADDRESS_SYNTAX
+- PT_BINARY
+3f07:
+- PR_CONTROL_ID
+- PT_BINARY
+80a0:
+- PR_EMS_AB_DXA_TYPES
+- PT_LONG
+3a4d:
+- PR_GENDER
+- PT_SHORT
+3f08:
+- PR_INITIAL_DETAILS_PANE
+- PT_LONG
+80a1:
+- PR_EMS_AB_DXA_UNCONF_CONTAINER_LIST
+- PT_OBJECT|PT_MV_TSTRING
+"8150":
+- PR_EMS_AB_TURN_REQUEST_THRESHOLD
+- PT_LONG
+3a4e:
+- PR_MANAGER_NAME
+- PT_TSTRING
+66fe:
+- PR_OWNER_NAME
+- PT_STRING8
+80a2:
+- PR_EMS_AB_ENCAPSULATION_METHOD
+- PT_LONG
+"8151":
+- PR_EMS_AB_TWO_WAY_ALTERNATE_FACILITY
+- PT_BOOLEAN
+"1090":
+- PR_FLAG_STATUS
+- PT_LONG
+667b:
+- PR_PROFILE_MOAB
+- PT_TSTRING
+80a3:
+- PR_EMS_AB_ENCRYPT
+- PT_BOOLEAN
+"8152":
+- PR_EMS_AB_UNAUTH_ORIG_BL
+- PT_OBJECT|PT_MV_TSTRING
+3a4f:
+- PR_NICKNAME
+- PT_TSTRING
+"3900":
+- PR_DISPLAY_TYPE
+- PT_LONG
+"1091":
+- PR_FLAG_COMPLETE
+- PT_SYSTIME
+66ff:
+- PR_ASSIGNED_ACCESS
+- PT_LONG
+667c:
+- PR_PROFILE_MOAB_GUID
+- PT_TSTRING
+80a4:
+- PR_EMS_AB_EXPAND_DLS_LOCALLY
+- PT_BOOLEAN
+"8153":
+- PR_EMS_AB_USER_PASSWORD
+- PT_MV_BINARY
+811a:
+- PR_EMS_AB_REMOTE_BRIDGE_HEAD
+- PT_TSTRING
+667d:
+- PR_PROFILE_MOAB_SEQ
+- PT_LONG
+80a5:
+- PR_EMS_AB_EXPORT_CONTAINERS
+- PT_OBJECT|PT_MV_TSTRING
+"8154":
+- PR_EMS_AB_USN_CREATED
+- PT_LONG
+"3902":
+- PR_TEMPLATEID
+- PT_BINARY
+811b:
+- PR_EMS_AB_REMOTE_BRIDGE_HEAD_ADDRESS
+- PT_TSTRING
+80a6:
+- PR_EMS_AB_EXPORT_CUSTOM_RECIPIENTS
+- PT_BOOLEAN
+"8023":
+- PR_EMS_AB_BUSINESS_ROLES
+- PT_BINARY
+"8155":
+- PR_EMS_AB_USN_DSA_LAST_OBJ_REMOVED
+- PT_LONG
+811c:
+- PR_EMS_AB_REMOTE_OUT_BH_SERVER
+- PT_OBJECT|PT_MV_TSTRING
+"4038":
+- PR_CREATOR_SIMPLE_DISP_NAME
+- PT_UNICODE
+667f:
+- PR_IMPLIED_RESTRICTIONS
+- PT_MV_BINARY
+80a7:
+- PR_EMS_AB_EXTENDED_CHARS_ALLOWED
+- PT_BOOLEAN
+"8024":
+- PR_EMS_AB_OWNER_BL
+- PT_OBJECT|PT_MV_TSTRING
+"8156":
+- PR_EMS_AB_USN_LAST_OBJ_REM
+- PT_LONG
+"3904":
+- PR_PRIMARY_CAPABILITY
+- PT_BINARY
+7c0a:
+- PR_STORE_SLOWLINK
+- PT_BOOLEAN
+811d:
+- PR_EMS_AB_REMOTE_SITE
+- PT_OBJECT|PT_MV_TSTRING
+80a8:
+- PR_EMS_AB_EXTENSION_DATA
+- PT_MV_BINARY
+"8025":
+- PR_EMS_AB_CROSS_CERTIFICATE_PAIR
+- PT_MV_BINARY
+"8157":
+- PR_EMS_AB_USN_SOURCE
+- PT_LONG
+811e:
+- PR_EMS_AB_REPLICATION_SENSITIVITY
+- PT_LONG
+80a9:
+- PR_EMS_AB_EXTENSION_NAME
+- PT_MV_TSTRING
+"8026":
+- PR_EMS_AB_AUTHORITY_REVOCATION_LIST
+- PT_MV_BINARY
+"8158":
+- PR_EMS_AB_X121_ADDRESS
+- PT_MV_TSTRING
+811f:
+- PR_EMS_AB_REPLICATION_STAGGER
+- PT_LONG
+3a5a:
+- PR_HOME_ADDRESS_COUNTRY
+- PT_TSTRING
+"8027":
+- PR_EMS_AB_ASSOC_NT_ACCOUNT
+- PT_BINARY
+"8159":
+- PR_EMS_AB_X25_CALL_USER_DATA_INCOMING
+- PT_BINARY
+3a5b:
+- PR_HOME_ADDRESS_POSTAL_CODE
+- PT_TSTRING
+"8028":
+- PR_EMS_AB_EXPIRATION_TIME
+- PT_SYSTIME
+3a5c:
+- PR_HOME_ADDRESS_STATE_OR_PROVINCE
+- PT_TSTRING
+"8029":
+- PR_EMS_AB_USN_CHANGED
+- PT_LONG
+400a:
+- PR_START_SUB_FLD
+- PT_LONG
+80b0:
+- PR_EMS_AB_GATEWAY_LOCAL_CRED
+- PT_TSTRING
+3a5d:
+- PR_HOME_ADDRESS_STREET
+- PT_TSTRING
+81ab:
+- PR_EMS_AB_HTTP_SERVERS
+- PT_MV_TSTRING
+400b:
+- PR_END_FOLDER
+- PT_LONG
+80b1:
+- PR_EMS_AB_GATEWAY_LOCAL_DESIG
+- PT_TSTRING
+"8160":
+- PR_EMS_AB_X400_ATTACHMENT_TYPE
+- PT_BINARY
+3a5e:
+- PR_HOME_ADDRESS_POST_OFFICE_BOX
+- PT_TSTRING
+81ac:
+- PR_EMS_AB_MODERATED
+- PT_BOOLEAN
+400c:
+- PR_START_MESSAGE
+- PT_LONG
+80b2:
+- PR_EMS_AB_GATEWAY_PROXY
+- PT_MV_TSTRING
+"8161":
+- PR_EMS_AB_X400_SELECTOR_SYNTAX
+- PT_LONG
+3a5f:
+- PR_OTHER_ADDRESS_CITY
+- PT_TSTRING
+81ad:
+- PR_EMS_AB_RAS_ACCOUNT
+- PT_TSTRING
+400d:
+- PR_END_MESSAGE
+- PT_LONG
+668b:
+- PR_NNTP_CONTROL_FOLDER_ENTRYID
+- PT_BINARY
+80b3:
+- PR_EMS_AB_GATEWAY_ROUTING_TREE
+- PT_BINARY
+"8030":
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_4
+- PT_TSTRING
+"8162":
+- PR_EMS_AB_X500_ACCESS_CONTROL_LIST
+- PT_BINARY
+81ae:
+- PR_EMS_AB_RAS_PASSWORD
+- PT_BINARY
+812a:
+- PR_EMS_AB_RUNS_ON
+- PT_OBJECT|PT_MV_TSTRING
+400e:
+- PR_END_ATTACH
+- PT_LONG
+668c:
+- PR_NEWSGROUP_ROOT_FOLDER_ENTRYID
+- PT_BINARY
+80b4:
+- PR_EMS_AB_GWART_LAST_MODIFIED
+- PT_SYSTIME
+"8031":
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_5
+- PT_TSTRING
+"8163":
+- PR_EMS_AB_XMIT_TIMEOUT_NON_URGENT
+- PT_LONG
+81af:
+- PR_EMS_AB_INCOMING_PASSWORD
+- PT_BINARY
+812b:
+- PR_EMS_AB_S_SELECTOR
+- PT_BINARY
+400f:
+- PR_EC_WARNING
+- PT_LONG
+668d:
+- PR_INBOUND_NEWSFEED_DN
+- PT_TSTRING
+80b5:
+- PR_EMS_AB_HAS_FULL_REPLICA_NCS
+- PT_OBJECT|PT_MV_TSTRING
+"8032":
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_6
+- PT_TSTRING
+"8164":
+- PR_EMS_AB_XMIT_TIMEOUT_NORMAL
+- PT_LONG
+812c:
+- PR_EMS_AB_S_SELECTOR_INBOUND
+- PT_BINARY
+668e:
+- PR_OUTBOUND_NEWSFEED_DN
+- PT_TSTRING
+80b6:
+- PR_EMS_AB_HAS_MASTER_NCS
+- PT_OBJECT|PT_MV_TSTRING
+"8033":
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_7
+- PT_TSTRING
+"8165":
+- PR_EMS_AB_XMIT_TIMEOUT_URGENT
+- PT_LONG
+812d:
+- PR_EMS_AB_SEARCH_FLAGS
+- PT_LONG
+668f:
+- PR_DELETED_ON
+- PT_SYSTIME
+80b7:
+- PR_EMS_AB_HEURISTICS
+- PT_LONG
+"8034":
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_8
+- PT_TSTRING
+"8166":
+- PR_EMS_AB_SITE_FOLDER_GUID
+- PT_BINARY
+812e:
+- PR_EMS_AB_SEARCH_GUIDE
+- PT_MV_BINARY
+80b8:
+- PR_EMS_AB_HIDE_DL_MEMBERSHIP
+- PT_BOOLEAN
+"8035":
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_9
+- PT_TSTRING
+"8167":
+- PR_EMS_AB_SITE_FOLDER_SERVER
+- PT_OBJECT|PT_MV_TSTRING
+812f:
+- PR_EMS_AB_SEE_ALSO
+- PT_OBJECT|PT_MV_TSTRING
+80b9:
+- PR_EMS_AB_HIDE_FROM_ADDRESS_BOOK
+- PT_BOOLEAN
+"8036":
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_10
+- PT_TSTRING
+"8168":
+- PR_EMS_AB_REPLICATION_MAIL_MSG_SIZE
+- PT_LONG
+"8037":
+- PR_EMS_AB_SECURITY_PROTOCOL
+- PT_MV_BINARY
+"8169":
+- PR_EMS_AB_MAXIMUM_OBJECT_ID
+- PT_BINARY
+"8038":
+- PR_EMS_AB_PF_CONTACTS
+- PT_OBJECT|PT_MV_TSTRING
+401a:
+- PR_SENT_REPRESENTING_FLAGS
+- PT_LONG
+65ea:
+- PR_RULE_MSG_USER_FLAGS
+- PT_LONG
+80c0:
+- PR_EMS_AB_IS_DELETED
+- PT_BOOLEAN
+81ba:
+- PR_EMS_AB_GROUP_BY_ATTR_4
+- PT_TSTRING
+401b:
+- PR_RCVD_BY_FLAGS
+- PT_LONG
+65eb:
+- PR_RULE_MSG_PROVIDER
+- PT_UNICODE
+80c1:
+- PR_EMS_AB_IS_SINGLE_VALUED
+- PT_BOOLEAN
+"8170":
+- PR_EMS_AB_NETWORK_ADDRESS
+- PT_MV_TSTRING
+401c:
+- PR_RCVD_REPRESENTING_FLAGS
+- PT_LONG
+65ec:
+- PR_RULE_MSG_NAME
+- PT_UNICODE
+669a:
+- PR_INTERNET_CHARSET
+- PT_TSTRING
+80c2:
+- PR_EMS_AB_KCC_STATUS
+- PT_MV_BINARY
+"8171":
+- PR_EMS_AB_LDAP_DISPLAY_NAME
+- PT_MV_TSTRING
+401d:
+- PR_ORIGINAL_SENDER_FLAGS
+- PT_LONG
+65ed:
+- PR_RULE_MSG_LEVEL
+- PT_LONG
+669b:
+- PR_DELETED_MESSAGE_SIZE
+- PT_I8
+80c3:
+- PR_EMS_AB_KNOWLEDGE_INFORMATION
+- PT_MV_TSTRING
+"8040":
+- PR_EMS_AB_ENCRYPT_ALG_LIST_NA
+- PT_MV_TSTRING
+401e:
+- PR_ORIGINAL_SENT_REPRESENTING_FLAGS
+- PT_LONG
+65ee:
+- PR_RULE_MSG_PROVIDER_DATA
+- PT_BINARY
+669c:
+- PR_DELETED_NORMAL_MESSAGE_SIZE
+- PT_I8
+80c4:
+- PR_EMS_AB_LINE_WRAP
+- PT_LONG
+"8041":
+- PR_EMS_AB_ENCRYPT_ALG_LIST_OTHER
+- PT_MV_TSTRING
+"8173":
+- PR_EMS_AB_SCHEMA_FLAGS
+- PT_LONG
+81be:
+- PR_EMS_AB_VIEW_SITE
+- PT_TSTRING
+813a:
+- PR_EMS_AB_STREET_ADDRESS
+- PT_TSTRING
+401f:
+- PR_REPORT_FLAGS
+- PT_LONG
+669d:
+- PR_DELETED_ASSOC_MESSAGE_SIZE
+- PT_I8
+80c5:
+- PR_EMS_AB_LINK_ID
+- PT_LONG
+"8042":
+- PR_EMS_AB_IMPORTED_FROM
+- PT_TSTRING
+"8174":
+- PR_EMS_AB_BRIDGEHEAD_SERVERS
+- PT_OBJECT|PT_MV_TSTRING
+81bf:
+- PR_EMS_AB_VIEW_CONTAINER_1
+- PT_TSTRING
+813b:
+- PR_EMS_AB_SUB_REFS
+- PT_OBJECT|PT_MV_TSTRING
+65ef:
+- PR_RULE_MSG_ACTIONS
+- PT_BINARY
+669e:
+- PR_SECURE_IN_SITE
+- PT_BOOLEAN
+80c6:
+- PR_EMS_AB_LOCAL_BRIDGE_HEAD
+- PT_TSTRING
+"8043":
+- PR_EMS_AB_ENCRYPT_ALG_SELECTED_NA
+- PT_TSTRING
+"8175":
+- PR_EMS_AB_WWW_HOME_PAGE
+- PT_TSTRING
+3e00:
+- PR_IDENTITY_DISPLAY
+- PT_TSTRING
+800a:
+- PR_EMS_AB_AUTOREPLY_MESSAGE
+- PT_TSTRING
+813c:
+- PR_EMS_AB_SUBMISSION_CONT_LENGTH
+- PT_LONG
+80c7:
+- PR_EMS_AB_LOCAL_BRIDGE_HEAD_ADDRESS
+- PT_TSTRING
+"8044":
+- PR_EMS_AB_ACCESS_CATEGORY
+- PT_LONG
+"8176":
+- PR_EMS_AB_NNTP_CONTENT_FORMAT
+- PT_TSTRING
+3e01:
+- PR_IDENTITY_ENTRYID
+- PT_BINARY
+800b:
+- PR_EMS_AB_AUTOREPLY
+- PT_BOOLEAN
+813d:
+- PR_EMS_AB_SUPPORTED_APPLICATION_CONTEXT
+- PT_MV_BINARY
+"4059":
+- PR_CREATOR_FLAGS
+- PT_LONG
+80c8:
+- PR_EMS_AB_LOCAL_INITIAL_TURN
+- PT_BOOLEAN
+"8045":
+- PR_EMS_AB_ACTIVATION_SCHEDULE
+- PT_BINARY
+"8177":
+- PR_EMS_AB_POP_CONTENT_FORMAT
+- PT_TSTRING
+3e02:
+- PR_RESOURCE_METHODS
+- PT_LONG
+800c:
+- PR_EMS_AB_OWNER
+- PT_OBJECT|PT_MV_TSTRING
+813e:
+- PR_EMS_AB_SUPPORTING_STACK
+- PT_OBJECT|PT_MV_TSTRING
+80c9:
+- PR_EMS_AB_LOCAL_SCOPE
+- PT_OBJECT|PT_MV_TSTRING
+"8046":
+- PR_EMS_AB_ACTIVATION_STYLE
+- PT_LONG
+"8178":
+- PR_EMS_AB_LANGUAGE
+- PT_LONG
+3e03:
+- PR_RESOURCE_TYPE
+- PT_LONG
+800d:
+- PR_EMS_AB_KM_SERVER
+- PT_OBJECT|PT_MV_TSTRING
+813f:
+- PR_EMS_AB_SUPPORTING_STACK_BL
+- PT_OBJECT|PT_MV_TSTRING
+"8047":
+- PR_EMS_AB_ADDRESS_ENTRY_DISPLAY_TABLE_MSDOS
+- PT_BINARY
+"8179":
+- PR_EMS_AB_POP_CHARACTER_SET
+- PT_TSTRING
+"4061":
+- PR_ORIGINATOR_SEARCH_KEY
+- PT_BINARY
+3e04:
+- PR_STATUS_CODE
+- PT_LONG
+800e:
+- PR_EMS_AB_REPORTS
+- PT_OBJECT
+"8048":
+- PR_EMS_AB_ADDRESS_TYPE
+- PT_TSTRING
+3e05:
+- PR_IDENTITY_SEARCH_KEY
+- PT_BINARY
+800f:
+- PR_EMS_AB_PROXY_ADDRESSES
+- PT_MV_TSTRING
+80d0:
+- PR_EMS_AB_MDB_MSG_TIME_OUT_PERIOD
+- PT_LONG
+"8049":
+- PR_EMS_AB_ADMD
+- PT_TSTRING
+3e06:
+- PR_OWN_STORE_ENTRYID
+- PT_BINARY
+80d1:
+- PR_EMS_AB_MDB_OVER_QUOTA_LIMIT
+- PT_LONG
+"8180":
+- PR_EMS_AB_CONNECTION_LIST_FILTER
+- PT_BINARY
+"4064":
+- PR_REPORT_DESTINATION_SEARCH_KEY
+- PT_BINARY
+3e07:
+- PR_RESOURCE_PATH
+- PT_TSTRING
+80d2:
+- PR_EMS_AB_MDB_STORAGE_QUOTA
+- PT_LONG
+"8181":
+- PR_EMS_AB_AVAILABLE_AUTHORIZATION_PACKAGES
+- PT_MV_TSTRING
+"4065":
+- PR_ER_FLAG
+- PT_LONG
+3e08:
+- PR_STATUS_STRING
+- PT_TSTRING
+402c:
+- PR_MESSAGE_SUBMISSION_ID_FROM_CLIENT
+- PT_BINARY
+80d3:
+- PR_EMS_AB_MDB_UNREAD_LIMIT
+- PT_LONG
+"8050":
+- PR_EMS_AB_ANCESTOR_ID
+- PT_BINARY
+"8182":
+- PR_EMS_AB_CHARACTER_SET_LIST
+- PT_MV_TSTRING
+3e09:
+- PR_X400_DEFERRED_DELIVERY_CANCEL
+- PT_BOOLEAN
+"8051":
+- PR_EMS_AB_ASSOC_REMOTE_DXA
+- PT_OBJECT|PT_MV_TSTRING
+"8183":
+- PR_EMS_AB_USE_SITE_VALUES
+- PT_BOOLEAN
+814a:
+- PR_EMS_AB_TRANSFER_TIMEOUT_NON_URGENT
+- PT_LONG
+80d4:
+- PR_EMS_AB_MDB_USE_DEFAULTS
+- PT_BOOLEAN
+"8052":
+- PR_EMS_AB_ASSOCIATION_LIFETIME
+- PT_LONG
+"8184":
+- PR_EMS_AB_ENABLED_AUTHORIZATION_PACKAGES
+- PT_MV_TSTRING
+814b:
+- PR_EMS_AB_TRANSFER_TIMEOUT_NORMAL
+- PT_LONG
+"4068":
+- PR_INTERNET_SUBJECT
+- PT_BINARY
+80d5:
+- PR_EMS_AB_MESSAGE_TRACKING_ENABLED
+- PT_BOOLEAN
+"8053":
+- PR_EMS_AB_AUTH_ORIG_BL
+- PT_OBJECT|PT_MV_TSTRING
+"8185":
+- PR_EMS_AB_CHARACTER_SET
+- PT_TSTRING
+814c:
+- PR_EMS_AB_TRANSFER_TIMEOUT_URGENT
+- PT_LONG
+"4069":
+- PR_INTERNET_SENT_REPRESENTING_NAME
+- PT_BINARY
+80d6:
+- PR_EMS_AB_MONITOR_CLOCK
+- PT_BOOLEAN
+"8054":
+- PR_EMS_AB_AUTHORIZED_DOMAIN
+- PT_TSTRING
+"8186":
+- PR_EMS_AB_CONTENT_TYPE
+- PT_LONG
+814d:
+- PR_EMS_AB_TRANSLATION_TABLE_USED
+- PT_LONG
+80d7:
+- PR_EMS_AB_MONITOR_SERVERS
+- PT_BOOLEAN
+"8187":
+- PR_EMS_AB_ANONYMOUS_ACCESS
+- PT_BOOLEAN
+814e:
+- PR_EMS_AB_TRANSPORT_EXPEDITED_DATA
+- PT_BOOLEAN
+8c18:
+- PR_EMS_AB_VIEW_FLAGS
+- PT_LONG
+80d8:
+- PR_EMS_AB_MONITOR_SERVICES
+- PT_BOOLEAN
+"8055":
+- PR_EMS_AB_AUTHORIZED_PASSWORD
+- PT_BINARY
+"8188":
+- PR_EMS_AB_CONTROL_MSG_FOLDER_ID
+- PT_BINARY
+814f:
+- PR_EMS_AB_TRUST_LEVEL
+- PT_LONG
+8c19:
+- PR_EMS_AB_GROUP_BY_ATTR_VALUE_STR
+- PT_TSTRING
+80d9:
+- PR_EMS_AB_MONITORED_CONFIGURATIONS
+- PT_OBJECT|PT_MV_TSTRING
+"8056":
+- PR_EMS_AB_AUTHORIZED_USER
+- PT_TSTRING
+"8189":
+- PR_EMS_AB_USENET_SITE_NAME
+- PT_TSTRING
+3fc9:
+- PR_LAST_CONFLICT
+- PT_BINARY
+"8057":
+- PR_EMS_AB_BUSINESS_CATEGORY
+- PT_MV_TSTRING
+"8058":
+- PR_EMS_AB_CAN_CREATE_PF
+- PT_OBJECT|PT_MV_TSTRING
+8c20:
+- PR_EMS_AB_INBOUND_ACCEPT_ALL
+- PT_BOOLEAN
+80e0:
+- PR_EMS_AB_MONITORING_CACHED_VIA_MAIL
+- PT_OBJECT|PT_MV_TSTRING
+3fd0:
+- PR_REPL_HEADER
+- PT_BINARY
+"8059":
+- PR_EMS_AB_CAN_CREATE_PF_BL
+- PT_OBJECT|PT_MV_TSTRING
+8c21:
+- PR_EMS_AB_ENABLED
+- PT_BOOLEAN
+80e1:
+- PR_EMS_AB_MONITORING_CACHED_VIA_RPC
+- PT_OBJECT|PT_MV_TSTRING
+"8190":
+- PR_EMS_AB_INCOMING_MSG_SIZE_LIMIT
+- PT_LONG
+8c22:
+- PR_EMS_AB_PRESERVE_INTERNET_CONTENT
+- PT_BOOLEAN
+80e2:
+- PR_EMS_AB_MONITORING_ESCALATION_PROCEDURE
+- PT_MV_BINARY
+"8191":
+- PR_EMS_AB_SEND_TNEF
+- PT_BOOLEAN
+3fd1:
+- PR_REPL_STATUS
+- PT_BINARY
+80aa:
+- PR_EMS_AB_EXTENSION_NAME_INHERITED
+- PT_MV_TSTRING
+403d:
+- PR_ORG_ADDR_TYPE
+- PT_UNICODE
+8c23:
+- PR_EMS_AB_DISABLE_DEFERRED_COMMIT
+- PT_BOOLEAN
+80e3:
+- PR_EMS_AB_MONITORING_HOTSITE_POLL_INTERVAL
+- PT_LONG
+"8060":
+- PR_EMS_AB_CAN_PRESERVE_DNS
+- PT_BOOLEAN
+"8192":
+- PR_EMS_AB_AUTHORIZED_PASSWORD_CONFIRM
+- PT_BINARY
+3fd2:
+- PR_REPL_CHANGES
+- PT_BINARY
+80ab:
+- PR_EMS_AB_FACSIMILE_TELEPHONE_NUMBER
+- PT_MV_BINARY
+403e:
+- PR_ORG_EMAIL_ADDR
+- PT_UNICODE
+8c24:
+- PR_EMS_AB_CLIENT_ACCESS_ENABLED
+- PT_BOOLEAN
+80e4:
+- PR_EMS_AB_MONITORING_HOTSITE_POLL_UNITS
+- PT_LONG
+"8061":
+- PR_EMS_AB_CLOCK_ALERT_OFFSET
+- PT_LONG
+"8193":
+- PR_EMS_AB_INBOUND_NEWSFEED
+- PT_TSTRING
+3fd3:
+- PR_REPL_RGM
+- PT_BINARY
+80ac:
+- PR_EMS_AB_FILE_VERSION
+- PT_BINARY
+815a:
+- PR_EMS_AB_X25_CALL_USER_DATA_OUTGOING
+- PT_BINARY
+8c25:
+- PR_EMS_AB_REQUIRE_SSL
+- PT_BOOLEAN
+80e5:
+- PR_EMS_AB_MONITORING_MAIL_UPDATE_INTERVAL
+- PT_LONG
+"8062":
+- PR_EMS_AB_CLOCK_ALERT_REPAIR
+- PT_BOOLEAN
+"8194":
+- PR_EMS_AB_NEWSFEED_TYPE
+- PT_LONG
+3fd4:
+- PR_RMI
+- PT_BINARY
+80ad:
+- PR_EMS_AB_FILTER_LOCAL_ADDRESSES
+- PT_BOOLEAN
+815b:
+- PR_EMS_AB_X25_FACILITIES_DATA_INCOMING
+- PT_BINARY
+"8063":
+- PR_EMS_AB_CLOCK_WARNING_OFFSET
+- PT_LONG
+80ae:
+- PR_EMS_AB_FOLDERS_CONTAINER
+- PT_OBJECT|PT_MV_TSTRING
+80e6:
+- PR_EMS_AB_MONITORING_MAIL_UPDATE_UNITS
+- PT_LONG
+815c:
+- PR_EMS_AB_X25_FACILITIES_DATA_OUTGOING
+- PT_BINARY
+"8195":
+- PR_EMS_AB_OUTBOUND_NEWSFEED
+- PT_TSTRING
+8c26:
+- PR_EMS_AB_ANONYMOUS_ACCOUNT
+- PT_TSTRING
+3fd5:
+- PR_INTERNAL_POST_REPLY
+- PT_BINARY
+"8064":
+- PR_EMS_AB_CLOCK_WARNING_REPAIR
+- PT_BOOLEAN
+80af:
+- PR_EMS_AB_GARBAGE_COLL_PERIOD
+- PT_LONG
+80e7:
+- PR_EMS_AB_MONITORING_NORMAL_POLL_INTERVAL
+- PT_LONG
+815d:
+- PR_EMS_AB_X25_LEASED_LINE_PORT
+- PT_BINARY
+"8196":
+- PR_EMS_AB_NEWSGROUP_LIST
+- PT_BINARY
+8c27:
+- PR_EMS_AB_CERTIFICATE_CHAIN_V3
+- PT_BINARY
+3fd6:
+- PR_NTSD_MODIFICATION_TIME
+- PT_SYSTIME
+"8065":
+- PR_EMS_AB_COMPUTER_NAME
+- PT_TSTRING
+80e8:
+- PR_EMS_AB_MONITORING_NORMAL_POLL_UNITS
+- PT_LONG
+815e:
+- PR_EMS_AB_X25_LEASED_OR_SWITCHED
+- PT_BOOLEAN
+"8197":
+- PR_EMS_AB_NNTP_DISTRIBUTIONS
+- PT_MV_TSTRING
+8c28:
+- PR_EMS_AB_CERTIFICATE_REVOCATION_LIST_V3
+- PT_BINARY
+802d:
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_1
+- PT_TSTRING
+3fd8:
+- PR_PREVIEW_UNREAD
+- PT_TSTRING
+"8066":
+- PR_EMS_AB_CONNECTED_DOMAINS
+- PT_MV_TSTRING
+80e9:
+- PR_EMS_AB_MONITORING_RECIPIENTS
+- PT_OBJECT|PT_MV_TSTRING
+815f:
+- PR_EMS_AB_X25_REMOTE_MTA_PHONE
+- PT_TSTRING
+"8198":
+- PR_EMS_AB_NEWSGROUP
+- PT_TSTRING
+8c29:
+- PR_EMS_AB_CERTIFICATE_REVOCATION_LIST_V1
+- PT_BINARY
+802e:
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_2
+- PT_TSTRING
+3fd9:
+- PR_PREVIEW
+- PT_TSTRING
+"8067":
+- PR_EMS_AB_CONTAINER_INFO
+- PT_LONG
+"8199":
+- PR_EMS_AB_MODERATOR
+- PT_TSTRING
+802f:
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_3
+- PT_TSTRING
+"8068":
+- PR_EMS_AB_COST
+- PT_LONG
+"8069":
+- PR_EMS_AB_COUNTRY_NAME
+- PT_TSTRING
+80f0:
+- PR_EMS_AB_MTA_LOCAL_DESIG
+- PT_TSTRING
+8c30:
+- PR_EMS_AB_CROSS_CERTIFICATE_CRL
+- PT_MV_BINARY
+"3000":
+- PR_ROWID
+- PT_LONG
+80f1:
+- PR_EMS_AB_N_ADDRESS
+- PT_BINARY
+8c31:
+- PR_EMS_AB_SEND_EMAIL_MESSAGE
+- PT_BOOLEAN
+"3001":
+- PR_DISPLAY_NAME
+- PT_TSTRING
+80ba:
+- PR_EMS_AB_IMPORT_CONTAINER
+- PT_OBJECT|PT_MV_TSTRING
+80f2:
+- PR_EMS_AB_N_ADDRESS_TYPE
+- PT_LONG
+8c32:
+- PR_EMS_AB_ENABLE_COMPATIBILITY
+- PT_BOOLEAN
+"3002":
+- PR_ADDRTYPE
+- PT_TSTRING
+3fe2:
+- PR_FOLDER_DESIGN_FLAGS
+- PT_LONG
+"8070":
+- PR_EMS_AB_DESTINATION_INDICATOR
+- PT_MV_TSTRING
+80bb:
+- PR_EMS_AB_IMPORT_SENSITIVITY
+- PT_LONG
+80f3:
+- PR_EMS_AB_NT_MACHINE_NAME
+- PT_TSTRING
+8c33:
+- PR_EMS_AB_SMIME_ALG_LIST_NA
+- PT_MV_TSTRING
+3fe3:
+- PR_DELEGATED_BY_RULE
+- PT_BOOLEAN
+"8071":
+- PR_EMS_AB_DIAGNOSTIC_REG_KEY
+- PT_TSTRING
+80bc:
+- PR_EMS_AB_INBOUND_SITES
+- PT_OBJECT|PT_MV_TSTRING
+80f4:
+- PR_EMS_AB_NUM_OF_OPEN_RETRIES
+- PT_LONG
+8c34:
+- PR_EMS_AB_SMIME_ALG_LIST_OTHER
+- PT_MV_TSTRING
+"3003":
+- PR_EMAIL_ADDRESS
+- PT_TSTRING
+3fe4:
+- PR_DESIGN_IN_PROGRESS
+- PT_BOOLEAN
+"8072":
+- PR_EMS_AB_DL_MEM_REJECT_PERMS_BL
+- PT_OBJECT|PT_MV_TSTRING
+80bd:
+- PR_EMS_AB_INSTANCE_TYPE
+- PT_LONG
+80f5:
+- PR_EMS_AB_NUM_OF_TRANSFER_RETRIES
+- PT_LONG
+8c35:
+- PR_EMS_AB_SMIME_ALG_SELECTED_NA
+- PT_TSTRING
+"3004":
+- PR_COMMENT
+- PT_TSTRING
+803a:
+- PR_EMS_AB_HELP_DATA16
+- PT_BINARY
+3fe5:
+- PR_SECURE_ORIGINATION
+- PT_BOOLEAN
+"8073":
+- PR_EMS_AB_DL_MEM_SUBMIT_PERMS_BL
+- PT_OBJECT|PT_MV_TSTRING
+80be:
+- PR_EMS_AB_INTERNATIONAL_ISDN_NUMBER
+- PT_MV_TSTRING
+80f6:
+- PR_EMS_AB_OBJECT_CLASS_CATEGORY
+- PT_LONG
+8c36:
+- PR_EMS_AB_SMIME_ALG_SELECTED_OTHER
+- PT_TSTRING
+"3005":
+- PR_DEPTH
+- PT_LONG
+803b:
+- PR_EMS_AB_HELP_FILE_NAME
+- PT_TSTRING
+3fe6:
+- PR_PUBLISH_IN_ADDRESS_BOOK
+- PT_BOOLEAN
+"8074":
+- PR_EMS_AB_DL_MEMBER_RULE
+- PT_MV_BINARY
+80bf:
+- PR_EMS_AB_INVOCATION_ID
+- PT_BINARY
+80f7:
+- PR_EMS_AB_OBJECT_VERSION
+- PT_LONG
+8c37:
+- PR_EMS_AB_DEFAULT_MESSAGE_FORMAT
+- PT_BOOLEAN
+"3006":
+- PR_PROVIDER_DISPLAY
+- PT_TSTRING
+803c:
+- PR_EMS_AB_OBJ_DIST_NAME
+- PT_OBJECT|PT_MV_TSTRING
+3fe7:
+- PR_RESOLVE_METHOD
+- PT_LONG
+3d00:
+- PR_STORE_PROVIDERS
+- PT_BINARY
+"8075":
+- PR_EMS_AB_DOMAIN_DEF_ALT_RECIP
+- PT_OBJECT|PT_MV_TSTRING
+80f8:
+- PR_EMS_AB_OFF_LINE_AB_CONTAINERS
+- PT_OBJECT|PT_MV_TSTRING
+8c38:
+- PR_EMS_AB_TYPE
+- PT_TSTRING
+"3007":
+- PR_CREATION_TIME
+- PT_SYSTIME
+803d:
+- PR_EMS_AB_ENCRYPT_ALG_SELECTED_OTHER
+- PT_TSTRING
+3fe8:
+- PR_ADDRESS_BOOK_DISPLAY_NAME
+- PT_TSTRING
+3d01:
+- PR_AB_PROVIDERS
+- PT_BINARY
+"8076":
+- PR_EMS_AB_DOMAIN_NAME
+- PT_TSTRING
+80f9:
+- PR_EMS_AB_OFF_LINE_AB_SCHEDULE
+- PT_BINARY
+"3008":
+- PR_LAST_MODIFICATION_TIME
+- PT_SYSTIME
+803e:
+- PR_EMS_AB_AUTOREPLY_SUBJECT
+- PT_TSTRING
+3fe9:
+- PR_EFORMS_LOCALE_ID
+- PT_LONG
+3d02:
+- PR_TRANSPORT_PROVIDERS
+- PT_BINARY
+"8077":
+- PR_EMS_AB_DSA_SIGNATURE
+- PT_BINARY
+"3009":
+- PR_RESOURCE_FLAGS
+- PT_LONG
+803f:
+- PR_EMS_AB_HOME_PUBLIC_SERVER
+- PT_OBJECT|PT_MV_TSTRING
+"8078":
+- PR_EMS_AB_DXA_ADMIN_COPY
+- PT_BOOLEAN
+3d04:
+- PR_DEFAULT_PROFILE
+- PT_BOOLEAN
+"8079":
+- PR_EMS_AB_DXA_ADMIN_FORWARD
+- PT_BOOLEAN
+3ff0:
+- PR_CONFLICT_ENTRYID
+- PT_BINARY
+8c40:
+- PR_EMS_AB_VOICE_MAIL_FLAGS
+- PT_MV_BINARY
+405a:
+- PR_MODIFIER_FLAGS
+- PT_LONG
+3d05:
+- PR_AB_SEARCH_PATH
+- PT_MV_BINARY
+3ff1:
+- PR_MESSAGE_LOCALE_ID
+- PT_LONG
+8c41:
+- PR_EMS_AB_VOICE_MAIL_VOLUME
+- PT_LONG
+405b:
+- PR_ORIGINATOR_FLAGS
+- PT_LONG
+3d06:
+- PR_AB_DEFAULT_DIR
+- PT_BINARY
+3ff2:
+- PR_RULE_TRIGGER_HISTORY
+- PT_BINARY
+80ca:
+- PR_EMS_AB_LOG_FILENAME
+- PT_TSTRING
+8c42:
+- PR_EMS_AB_VOICE_MAIL_SPEED
+- PT_LONG
+405c:
+- PR_REPORT_DESTINATION_FLAGS
+- PT_LONG
+"8080":
+- PR_EMS_AB_DXA_EXCHANGE_OPTIONS
+- PT_LONG
+3d07:
+- PR_AB_DEFAULT_PAB
+- PT_BINARY
+80cb:
+- PR_EMS_AB_LOG_ROLLOVER_INTERVAL
+- PT_LONG
+8c43:
+- PR_EMS_AB_VOICE_MAIL_RECORDING_LENGTH
+- PT_MV_LONG
+405d:
+- PR_ORIGINAL_AUTHOR_FLAGS
+- PT_LONG
+"6800":
+- PR_MAILBEAT_BOUNCE_SERVER
+- PT_UNICODE
+3ff3:
+- PR_MOVE_TO_STORE_ENTRYID
+- PT_BINARY
+"8081":
+- PR_EMS_AB_DXA_EXPORT_NOW
+- PT_BOOLEAN
+3d08:
+- PR_FILTERING_HOOKS
+- PT_BINARY
+80cc:
+- PR_EMS_AB_MAINTAIN_AUTOREPLY_HISTORY
+- PT_BOOLEAN
+817a:
+- PR_EMS_AB_USN_INTERSITE
+- PT_LONG
+8c44:
+- PR_EMS_AB_DISPLAY_NAME_SUFFIX
+- PT_TSTRING
+"6801":
+- PR_MAILBEAT_REQUEST_SENT
+- PT_SYSTIME
+3ff4:
+- PR_MOVE_TO_FOLDER_ENTRYID
+- PT_BINARY
+"8082":
+- PR_EMS_AB_DXA_FLAGS
+- PT_LONG
+3d09:
+- PR_SERVICE_NAME
+- PT_TSTRING
+80cd:
+- PR_EMS_AB_MAPI_DISPLAY_TYPE
+- PT_LONG
+817b:
+- PR_EMS_AB_SUB_SITE
+- PT_TSTRING
+8c45:
+- PR_EMS_AB_ATTRIBUTE_CERTIFICATE
+- PT_MV_BINARY
+"6802":
+- PR_USENET_SITE_NAME
+- PT_UNICODE
+3ff5:
+- PR_STORAGE_QUOTA_LIMIT
+- PT_LONG
+"8083":
+- PR_EMS_AB_DXA_IMP_SEQ
+- PT_TSTRING
+804a:
+- PR_EMS_AB_ADMIN_DESCRIPTION
+- PT_TSTRING
+80ce:
+- PR_EMS_AB_MAPI_ID
+- PT_LONG
+817c:
+- PR_EMS_AB_SCHEMA_VERSION
+- PT_MV_LONG
+8c46:
+- PR_EMS_AB_DELTA_REVOCATION_LIST
+- PT_MV_BINARY
+"6803":
+- PR_MAILBEAT_REQUEST_RECEIVED
+- PT_SYSTIME
+3ff6:
+- PR_EXCESS_STORAGE_USED
+- PT_LONG
+"3700":
+- PR_ATTACHMENT_X400_PARAMETERS
+- PT_BINARY
+"8084":
+- PR_EMS_AB_DXA_IMP_SEQ_TIME
+- PT_SYSTIME
+0e00:
+- PR_CURRENT_VERSION
+- PT_I8
+804b:
+- PR_EMS_AB_ADMIN_DISPLAY_NAME
+- PT_TSTRING
+80cf:
+- PR_EMS_AB_MDB_BACKOFF_INTERVAL
+- PT_LONG
+817d:
+- PR_EMS_AB_NNTP_CHARACTER_SET
+- PT_TSTRING
+8c47:
+- PR_EMS_AB_SECURITY_POLICY
+- PT_MV_BINARY
+"6804":
+- PR_MAILBEAT_REQUEST_PROCESSED
+- PT_SYSTIME
+3ff7:
+- PR_SVR_GENERATING_QUOTA_MSG
+- PT_TSTRING
+3d10:
+- PR_SERVICE_DELETE_FILES
+- PT_MV_TSTRING
+"8085":
+- PR_EMS_AB_DXA_IMP_SEQ_USN
+- PT_LONG
+0e01:
+- PR_DELETE_AFTER_SUBMIT
+- PT_BOOLEAN
+804c:
+- PR_EMS_AB_ADMIN_EXTENSION_DLL
+- PT_TSTRING
+817e:
+- PR_EMS_AB_USE_SERVER_VALUES
+- PT_BOOLEAN
+8c48:
+- PR_EMS_AB_SUPPORT_SMIME_SIGNATURES
+- PT_BOOLEAN
+"3701":
+- PR_ATTACH_DATA
+- PT_OBJECT|PT_BINARY
+3ff8:
+- PR_CREATOR_NAME
+- PT_TSTRING
+"3702":
+- PR_ATTACH_ENCODING
+- PT_BINARY
+3d11:
+- PR_AB_SEARCH_PATH_UPDATE
+- PT_BINARY
+"8086":
+- PR_EMS_AB_DXA_IMPORT_NOW
+- PT_BOOLEAN
+0e02:
+- PR_DISPLAY_BCC
+- PT_TSTRING
+3e0a:
+- PR_HEADER_FOLDER_ENTRYID
+- PT_BINARY
+804d:
+- PR_EMS_AB_ALIASED_OBJECT_NAME
+- PT_OBJECT|PT_MV_TSTRING
+817f:
+- PR_EMS_AB_ENABLED_PROTOCOLS
+- PT_LONG
+8c49:
+- PR_EMS_AB_DELEGATE_USER
+- PT_BOOLEAN
+"6806":
+- PR_MAILBEAT_REPLY_SENT
+- PT_SYSTIME
+3ff9:
+- PR_CREATOR_ENTRYID
+- PT_BINARY
+"3703":
+- PR_ATTACH_EXTENSION
+- PT_TSTRING
+3d12:
+- PR_PROFILE_NAME
+- PT_TSTRING
+"8087":
+- PR_EMS_AB_DXA_IN_TEMPLATE_MAP
+- PT_MV_TSTRING
+0e03:
+- PR_DISPLAY_CC
+- PT_TSTRING
+3e0b:
+- PR_REMOTE_PROGRESS
+- PT_LONG
+804e:
+- PR_EMS_AB_ALT_RECIPIENT
+- PT_OBJECT|PT_MV_TSTRING
+"6807":
+- PR_MAILBEAT_REPLY_SUBMIT
+- PT_SYSTIME
+"3704":
+- PR_ATTACH_FILENAME
+- PT_TSTRING
+"8088":
+- PR_EMS_AB_DXA_LOCAL_ADMIN
+- PT_OBJECT|PT_MV_TSTRING
+0e04:
+- PR_DISPLAY_TO
+- PT_TSTRING
+3e0c:
+- PR_REMOTE_PROGRESS_TEXT
+- PT_TSTRING
+804f:
+- PR_EMS_AB_ALT_RECIPIENT_BL
+- PT_OBJECT|PT_MV_TSTRING
+"6808":
+- PR_MAILBEAT_REPLY_RECEIVED
+- PT_SYSTIME
+"3705":
+- PR_ATTACH_METHOD
+- PT_LONG
+"8089":
+- PR_EMS_AB_DXA_LOGGING_LEVEL
+- PT_LONG
+0e05:
+- PR_PARENT_DISPLAY
+- PT_TSTRING
+3e0d:
+- PR_REMOTE_VALIDATE_OK
+- PT_BOOLEAN
+8c50:
+- PR_EMS_AB_LIST_PUBLIC_FOLDERS
+- PT_BOOLEAN
+"6809":
+- PR_MAILBEAT_REPLY_PROCESSED
+- PT_SYSTIME
+0e06:
+- PR_MESSAGE_DELIVERY_TIME
+- PT_SYSTIME
+8c51:
+- PR_EMS_AB_LABELEDURI
+- PT_TSTRING
+"3707":
+- PR_ATTACH_LONG_FILENAME
+- PT_TSTRING
+0e07:
+- PR_MESSAGE_FLAGS
+- PT_LONG
+80da:
+- PR_EMS_AB_MONITORED_SERVERS
+- PT_OBJECT|PT_MV_TSTRING
+8c1a:
+- PR_EMS_AB_GROUP_BY_ATTR_VALUE_DN
+- PT_OBJECT|PT_MV_TSTRING
+8c52:
+- PR_EMS_AB_RETURN_EXACT_MSG_SIZE
+- PT_BOOLEAN
+"3708":
+- PR_ATTACH_PATHNAME
+- PT_TSTRING
+"8090":
+- PR_EMS_AB_DXA_PREV_REMOTE_ENTRIES
+- PT_OBJECT|PT_MV_TSTRING
+80db:
+- PR_EMS_AB_MONITORED_SERVICES
+- PT_MV_TSTRING
+8c1b:
+- PR_EMS_AB_VIEW_DEFINITION
+- PT_MV_BINARY
+8c53:
+- PR_EMS_AB_GENERATION_QUALIFIER
+- PT_TSTRING
+3fca:
+- PR_CONFLICT_MSG_KEY
+- PT_BINARY
+0e08:
+- PR_MESSAGE_SIZE
+- PT_LONG|PT_I8
+"3709":
+- PR_ATTACH_RENDERING
+- PT_BINARY
+0e09:
+- PR_PARENT_ENTRYID
+- PT_BINARY
+"8091":
+- PR_EMS_AB_DXA_PREV_REPLICATION_SENSITIVITY
+- PT_LONG
+80dc:
+- PR_EMS_AB_MONITORING_ALERT_DELAY
+- PT_LONG
+818a:
+- PR_EMS_AB_CONTROL_MSG_RULES
+- PT_BINARY
+8c1c:
+- PR_EMS_AB_MIME_TYPES
+- PT_BINARY
+8c54:
+- PR_EMS_AB_HOUSE_IDENTIFIER
+- PT_TSTRING
+3f80:
+- PR_DID
+- PT_I8
+"8092":
+- PR_EMS_AB_DXA_PREV_TEMPLATE_OPTIONS
+- PT_LONG
+80dd:
+- PR_EMS_AB_MONITORING_ALERT_UNITS
+- PT_LONG
+818b:
+- PR_EMS_AB_AVAILABLE_DISTRIBUTIONS
+- PT_TSTRING
+8c1d:
+- PR_EMS_AB_LDAP_SEARCH_CFG
+- PT_LONG
+8c55:
+- PR_EMS_AB_SUPPORTED_ALGORITHMS
+- PT_BINARY
+3f81:
+- PR_SEQID
+- PT_I8
+805a:
+- PR_EMS_AB_CAN_CREATE_PF_DL
+- PT_OBJECT|PT_MV_TSTRING
+"8093":
+- PR_EMS_AB_DXA_PREV_TYPES
+- PT_LONG
+80de:
+- PR_EMS_AB_MONITORING_AVAILABILITY_STYLE
+- PT_LONG
+8c1e:
+- PR_EMS_AB_INBOUND_DN
+- PT_OBJECT|PT_MV_TSTRING
+8c56:
+- PR_EMS_AB_DMD_NAME
+- PT_TSTRING
+3f82:
+- PR_DRAFTID
+- PT_I8
+805b:
+- PR_EMS_AB_CAN_CREATE_PF_DL_BL
+- PT_OBJECT|PT_MV_TSTRING
+"3710":
+- PR_ATTACH_MIME_SEQUENCE
+- PT_LONG
+0e10:
+- PR_SPOOLER_STATUS
+- PT_LONG
+"8094":
+- PR_EMS_AB_DXA_RECIPIENT_CP
+- PT_TSTRING
+80df:
+- PR_EMS_AB_MONITORING_AVAILABILITY_WINDOW
+- PT_BINARY
+818d:
+- PR_EMS_AB_OUTBOUND_HOST
+- PT_BINARY
+8c57:
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_11
+- PT_TSTRING
+3f83:
+- PR_CHECK_IN_TIME
+- PT_SYSTIME
+805c:
+- PR_EMS_AB_CAN_NOT_CREATE_PF
+- PT_OBJECT|PT_MV_TSTRING
+0e11:
+- PR_TRANSPORT_STATUS
+- PT_LONG
+"8095":
+- PR_EMS_AB_DXA_REMOTE_CLIENT
+- PT_OBJECT|PT_MV_TSTRING
+818e:
+- PR_EMS_AB_INBOUND_HOST
+- PT_MV_TSTRING
+8c1f:
+- PR_EMS_AB_INBOUND_NEWSFEED_TYPE
+- PT_BOOLEAN
+8c58:
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_12
+- PT_TSTRING
+3f84:
+- PR_CHECK_IN_COMMENT
+- PT_UNICODE
+805d:
+- PR_EMS_AB_CAN_NOT_CREATE_PF_BL
+- PT_OBJECT|PT_MV_TSTRING
+"3712":
+- PR_ATTACH_CONTENT_ID
+- PT_TSTRING
+0e12:
+- PR_MESSAGE_RECIPIENTS
+- PT_OBJECT
+"8096":
+- PR_EMS_AB_DXA_REQ_SEQ
+- PT_TSTRING
+818f:
+- PR_EMS_AB_OUTGOING_MSG_SIZE_LIMIT
+- PT_LONG
+8c59:
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_13
+- PT_TSTRING
+3d21:
+- PR_ADMIN_SECURITY_DESCRIPTOR
+- PT_BINARY
+3f85:
+- PR_VERSION_OP_CODE
+- PT_LONG
+805e:
+- PR_EMS_AB_CAN_NOT_CREATE_PF_DL
+- PT_OBJECT|PT_MV_TSTRING
+"3713":
+- PR_ATTACH_CONTENT_LOCATION
+- PT_TSTRING
+0e13:
+- PR_MESSAGE_ATTACHMENTS
+- PT_OBJECT
+"8097":
+- PR_EMS_AB_DXA_REQ_SEQ_TIME
+- PT_SYSTIME
+3f86:
+- PR_VERSION_OP_DATA
+- PT_BINARY
+805f:
+- PR_EMS_AB_CAN_NOT_CREATE_PF_DL_BL
+- PT_OBJECT|PT_MV_TSTRING
+"3714":
+- PR_ATTACH_FLAGS
+- PT_LONG
+0e14:
+- PR_SUBMIT_FLAGS
+- PT_LONG
+"8098":
+- PR_EMS_AB_DXA_REQ_SEQ_USN
+- PT_LONG
+3f87:
+- PR_VERSION_SEQUENCE_NUMBER
+- PT_LONG
+0e15:
+- PR_RECIPIENT_STATUS
+- PT_LONG
+"8099":
+- PR_EMS_AB_DXA_REQNAME
+- PT_TSTRING
+8c60:
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_14
+- PT_TSTRING
+3f88:
+- PR_ATTACH_ID
+- PT_I8
+"6001":
+- PR_DOTSTUFF_STATE
+- PT_LONG
+0e16:
+- PR_TRANSPORT_KEY
+- PT_LONG
+8c61:
+- PR_EMS_AB_EXTENSION_ATTRIBUTE_15
+- PT_TSTRING
+"3716":
+- PR_ATTACH_CONTENT_DISPOSITION
+- PT_UNICODE
+0e17:
+- PR_MSG_STATUS
+- PT_LONG
+80ea:
+- PR_EMS_AB_MONITORING_RECIPIENTS_NDR
+- PT_OBJECT|PT_MV_TSTRING
+8c62:
+- PR_EMS_AB_REPLICATED_OBJECT_VERSION
+- PT_LONG
+3fda:
+- PR_ABSTRACT
+- PT_TSTRING
+0e18:
+- PR_MESSAGE_DOWNLOAD_TIME
+- PT_LONG
+80eb:
+- PR_EMS_AB_MONITORING_RPC_UPDATE_INTERVAL
+- PT_LONG
+8c63:
+- PR_EMS_AB_MAIL_DROP
+- PT_TSTRING
+3fdb:
+- PR_DL_REPORT_FLAGS
+- PT_LONG
+0e19:
+- PR_CREATION_VERSION
+- PT_I8
+80ec:
+- PR_EMS_AB_MONITORING_RPC_UPDATE_UNITS
+- PT_LONG
+819a:
+- PR_EMS_AB_AUTHENTICATION_TO_USE
+- PT_TSTRING
+8c64:
+- PR_EMS_AB_FORWARDING_ADDRESS
+- PT_TSTRING
+3f90:
+- PR_VERSIONING_FLAGS
+- PT_SHORT
+3fdc:
+- PR_BILATERAL_INFO
+- PT_BINARY
+80ed:
+- PR_EMS_AB_MONITORING_WARNING_DELAY
+- PT_LONG
+819b:
+- PR_EMS_AB_HTTP_PUB_GAL
+- PT_BOOLEAN
+8c65:
+- PR_EMS_AB_FORM_DATA
+- PT_BINARY
+3f91:
+- PR_PKM_LAST_UNAPPROVED_VID
+- PT_BINARY
+3fdd:
+- PR_MSG_BODY_ID
+- PT_LONG
+806a:
+- PR_EMS_AB_DELIV_CONT_LENGTH
+- PT_LONG
+80ee:
+- PR_EMS_AB_MONITORING_WARNING_UNITS
+- PT_LONG
+819c:
+- PR_EMS_AB_HTTP_PUB_GAL_LIMIT
+- PT_LONG
+8c66:
+- PR_EMS_AB_OWA_SERVER
+- PT_TSTRING
+3f92:
+- PR_MV_PKM_VERSION_LABELS
+- PT_MV_UNICODE
+0e20:
+- PR_ATTACH_SIZE
+- PT_LONG
+3fde:
+- PR_INTERNET_CPID
+- PT_LONG
+806b:
+- PR_EMS_AB_DELIV_EITS
+- PT_MV_BINARY
+80ef:
+- PR_EMS_AB_MTA_LOCAL_CRED
+- PT_TSTRING
+8c67:
+- PR_EMS_AB_EMPLOYEE_NUMBER
+- PT_TSTRING
+3f93:
+- PR_MV_PKM_VERSION_STATUS
+- PT_MV_UNICODE
+0e21:
+- PR_ATTACH_NUM
+- PT_LONG
+3fdf:
+- PR_AUTO_RESPONSE_SUPPRESS
+- PT_LONG
+806c:
+- PR_EMS_AB_DELIV_EXT_CONT_TYPES
+- PT_MV_BINARY
+819e:
+- PR_EMS_AB_HTTP_PUB_PF
+- PT_MV_BINARY
+8c68:
+- PR_EMS_AB_TELEPHONE_PERSONAL_PAGER
+- PT_TSTRING
+3f94:
+- PR_PKM_INTERNAL_DATA
+- PT_BINARY
+0e22:
+- PR_PREPROCESS
+- PT_BOOLEAN
+806d:
+- PR_EMS_AB_DELIVER_AND_REDIRECT
+- PT_BOOLEAN
+8c69:
+- PR_EMS_AB_EMPLOYEE_TYPE
+- PT_TSTRING
+806e:
+- PR_EMS_AB_DELIVERY_MECHANISM
+- PT_LONG
+0e23:
+- PR_INTERNET_ARTICLE_NUMBER
+- PT_LONG
+806f:
+- PR_EMS_AB_DESCRIPTION
+- PT_MV_TSTRING
+0e24:
+- PR_NEWSGROUP_NAME
+- PT_TSTRING
+0e25:
+- PR_ORIGINATING_MTA_CERTIFICATE
+- PT_BINARY
+0e26:
+- PR_PROOF_OF_SUBMISSION
+- PT_BINARY
+80fa:
+- PR_EMS_AB_OFF_LINE_AB_SERVER
+- PT_OBJECT|PT_MV_TSTRING
+8c3a:
+- PR_EMS_AB_DO_OAB_VERSION
+- PT_LONG
+0e27:
+- PR_NT_SECURITY_DESCRIPTOR
+- PT_BINARY
+3fea:
+- PR_HAS_DAMS
+- PT_BOOLEAN
+80fb:
+- PR_EMS_AB_OFF_LINE_AB_STYLE
+- PT_LONG
+8c3b:
+- PR_EMS_AB_VOICE_MAIL_SYSTEM_GUID
+- PT_BINARY
+"0001":
+- PR_ACKNOWLEDGEMENT_MODE
+- PT_LONG
+300a:
+- PR_PROVIDER_DLL_NAME
+- PT_TSTRING
+3feb:
+- PR_DEFERRED_SEND_NUMBER
+- PT_LONG
+80fc:
+- PR_EMS_AB_OID_TYPE
+- PT_LONG
+8c3c:
+- PR_EMS_AB_VOICE_MAIL_USER_ID
+- PT_TSTRING
+"0002":
+- PR_ALTERNATE_RECIPIENT_ALLOWED
+- PT_BOOLEAN
+300b:
+- PR_SEARCH_KEY
+- PT_BINARY
+3fec:
+- PR_DEFERRED_SEND_UNITS
+- PT_LONG
+80fd:
+- PR_EMS_AB_OM_OBJECT_CLASS
+- PT_BINARY
+8c3d:
+- PR_EMS_AB_VOICE_MAIL_PASSWORD
+- PT_TSTRING
+"0003":
+- PR_AUTHORIZING_USERS
+- PT_BINARY
+807a:
+- PR_EMS_AB_DXA_ADMIN_UPDATE
+- PT_LONG
+"6701":
+- PR_PST_REMEMBER_PW
+- PT_BOOLEAN
+300c:
+- PR_PROVIDER_UID
+- PT_BINARY
+3fed:
+- PR_EXPIRY_NUMBER
+- PT_LONG
+80fe:
+- PR_EMS_AB_OM_SYNTAX
+- PT_LONG
+8c3e:
+- PR_EMS_AB_VOICE_MAIL_RECORDED_NAME
+- PT_BINARY
+"0004":
+- PR_AUTO_FORWARD_COMMENT
+- PT_TSTRING
+807b:
+- PR_EMS_AB_DXA_APPEND_REQCN
+- PT_BOOLEAN
+300d:
+- PR_PROVIDER_ORDINAL
+- PT_LONG
+3fee:
+- PR_EXPIRY_UNITS
+- PT_LONG
+80ff:
+- PR_EMS_AB_OOF_REPLY_TO_ORIGINATOR
+- PT_BOOLEAN
+8c3f:
+- PR_EMS_AB_VOICE_MAIL_GREETINGS
+- PT_MV_TSTRING
+"6702":
+- PR_OST_ENCRYPTION
+- PT_LONG
+3fef:
+- PR_DEFERRED_SEND_TIME
+- PT_SYSTIME
+"0005":
+- PR_AUTO_FORWARDED
+- PT_BOOLEAN
+807c:
+- PR_EMS_AB_DXA_CONF_CONTAINER_LIST
+- PT_OBJECT|PT_MV_TSTRING
+"6703":
+- PR_PST_PW_SZ_OLD
+- PT_TSTRING
+"0006":
+- PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID
+- PT_BINARY
+807d:
+- PR_EMS_AB_DXA_CONF_REQ_TIME
+- PT_SYSTIME
+"6704":
+- PR_PST_PW_SZ_NEW
+- PT_TSTRING
+"3600":
+- PR_CONTAINER_FLAGS
+- PT_LONG
+"0007":
+- PR_CONTENT_CORRELATOR
+- PT_BINARY
+807e:
+- PR_EMS_AB_DXA_CONF_SEQ
+- PT_TSTRING
+"6705":
+- PR_SORT_LOCALE_ID
+- PT_LONG
+"3601":
+- PR_FOLDER_TYPE
+- PT_LONG
+0008:
+- PR_CONTENT_IDENTIFIER
+- PT_TSTRING
+3d0a:
+- PR_SERVICE_DLL_NAME
+- PT_TSTRING
+807f:
+- PR_EMS_AB_DXA_CONF_SEQ_USN
+- PT_LONG
+"3602":
+- PR_CONTENT_COUNT
+- PT_LONG
+0009:
+- PR_CONTENT_LENGTH
+- PT_LONG
+3d0b:
+- PR_SERVICE_ENTRY_NAME
+- PT_TSTRING
+"3603":
+- PR_CONTENT_UNREAD
+- PT_LONG
+"6707":
+- PR_URL_NAME
+- PT_UNICODE
+3d0c:
+- PR_SERVICE_UID
+- PT_BINARY
+"3604":
+- PR_CREATE_TEMPLATES
+- PT_OBJECT
+3d0d:
+- PR_SERVICE_EXTRA_UIDS
+- PT_BINARY
+"0010":
+- PR_DELIVER_TIME
+- PT_SYSTIME
+"3605":
+- PR_DETAILS_TABLE
+- PT_OBJECT
+"6709":
+- PR_LOCAL_COMMIT_TIME
+- PT_SYSTIME
+3d0e:
+- PR_SERVICES
+- PT_BINARY
+3ffa:
+- PR_LAST_MODIFIER_NAME
+- PT_TSTRING
+f000:
+- PR_EMS_AB_OTHER_RECIPS
+- PT_OBJECT
+3d0f:
+- PR_SERVICE_SUPPORT_FILES
+- PT_MV_TSTRING
+3ffb:
+- PR_LAST_MODIFIER_ENTRYID
+- PT_BINARY
+"0011":
+- PR_DISCARD_REASON
+- PT_LONG
+"3607":
+- PR_SEARCH
+- PT_OBJECT
+3ffc:
+- PR_REPLY_RECIPIENT_SMTP_PROXIES
+- PT_TSTRING
+"0012":
+- PR_DISCLOSURE_OF_RECIPIENTS
+- PT_BOOLEAN
+"6710":
+- PR_URL_COMP_NAME_HASH
+- PT_LONG
+3ffd:
+- PR_MESSAGE_CODEPAGE
+- PT_LONG
+"0013":
+- PR_DL_EXPANSION_HISTORY
+- PT_BINARY
+"3609":
+- PR_SELECTABLE
+- PT_BOOLEAN
+808a:
+- PR_EMS_AB_DXA_NATIVE_ADDRESS_TYPE
+- PT_TSTRING
+0ff4:
+- PR_ACCESS
+- PT_LONG
+"6711":
+- PR_MSG_FOLDER_TEMPLATE_RES_2
+- PT_LONG
+3ffe:
+- PR_EXTENDED_ACL_DATA
+- PT_BINARY
+"0014":
+- PR_DL_EXPANSION_PROHIBITED
+- PT_BOOLEAN
+808b:
+- PR_EMS_AB_DXA_OUT_TEMPLATE_MAP
+- PT_MV_TSTRING
+0ff5:
+- PR_ROW_TYPE
+- PT_LONG
+"6712":
+- PR_RANK
+- PT_LONG
+"6844":
+- PR_DELEGATES_DISPLAY_NAMES
+- PT_MV_UNICODE
+"0015":
+- PR_EXPIRY_TIME
+- PT_SYSTIME
+808c:
+- PR_EMS_AB_DXA_PASSWORD
+- PT_TSTRING
+0ff6:
+- PR_INSTANCE_KEY
+- PT_BINARY
+3fff:
+- PR_FROM_I_HAVE
+- PT_BOOLEAN
+"6713":
+- PR_MSG_FOLDER_TEMPLATE_RES_4
+- PT_BOOLEAN
+"6845":
+- PR_DELEGATES_ENTRYIDS
+- PT_MV_BINARY
+"0016":
+- PR_IMPLICIT_CONVERSION_PROHIBITED
+- PT_BOOLEAN
+370a:
+- PR_ATTACH_TAG
+- PT_BINARY
+808d:
+- PR_EMS_AB_DXA_PREV_EXCHANGE_OPTIONS
+- PT_LONG
+"3610":
+- PR_FOLDER_ASSOCIATED_CONTENTS
+- PT_OBJECT
+0ff7:
+- PR_ACCESS_LEVEL
+- PT_LONG
+"6714":
+- PR_MSG_FOLDER_TEMPLATE_RES_5
+- PT_BOOLEAN
+"0017":
+- PR_IMPORTANCE
+- PT_LONG
+370b:
+- PR_RENDERING_POSITION
+- PT_LONG
+808e:
+- PR_EMS_AB_DXA_PREV_EXPORT_NATIVE_ONLY
+- PT_BOOLEAN
+0e0a:
+- PR_SENTMAIL_ENTRYID
+- PT_BINARY
+"3611":
+- PR_DEF_CREATE_DL
+- PT_BINARY
+0ff8:
+- PR_MAPPING_SIGNATURE
+- PT_BINARY
+"6715":
+- PR_MSG_FOLDER_TEMPLATE_RES_6
+- PT_BOOLEAN
+"6847":
+- PR_FREEBUSY_START_RANGE
+- PT_LONG
+0018:
+- PR_IPM_ID
+- PT_BINARY
+370c:
+- PR_ATTACH_TRANSPORT_NAME
+- PT_TSTRING
+808f:
+- PR_EMS_AB_DXA_PREV_IN_EXCHANGE_SENSITIVITY
+- PT_LONG
+"3612":
+- PR_DEF_CREATE_MAILUSER
+- PT_BINARY
+0ff9:
+- PR_RECORD_KEY
+- PT_BINARY
+"6716":
+- PR_MSG_FOLDER_TEMPLATE_RES_7
+- PT_BINARY
+"6848":
+- PR_FREEBUSY_END_RANGE
+- PT_LONG
+0019:
+- PR_LATEST_DELIVERY_TIME
+- PT_SYSTIME
+370d:
+- PR_ATTACH_LONG_PATHNAME
+- PT_TSTRING
+0e0c:
+- PR_CORRELATE
+- PT_BOOLEAN
+"3613":
+- PR_CONTAINER_CLASS
+- PT_TSTRING
+"6717":
+- PR_MSG_FOLDER_TEMPLATE_RES_8
+- PT_BINARY
+"6849":
+- PR_FREEBUSY_EMAIL_ADDRESS
+- PT_UNICODE
+370e:
+- PR_ATTACH_MIME_TAG
+- PT_TSTRING
+0e0d:
+- PR_CORRELATE_MTSID
+- PT_BINARY
+"3614":
+- PR_CONTAINER_MODIFY_VERSION
+- PT_I8
+"6718":
+- PR_MSG_FOLDER_TEMPLATE_RES_9
+- PT_BINARY
+370f:
+- PR_ATTACH_ADDITIONAL_INFO
+- PT_BINARY
+0e0e:
+- PR_DISCRETE_VALUES
+- PT_BOOLEAN
+"0020":
+- PR_ORIGINALLY_INTENDED_RECIPIENT_NAME
+- PT_BINARY
+"3615":
+- PR_AB_PROVIDER_ID
+- PT_BINARY
+"6719":
+- PR_MSG_FOLDER_TEMPLATE_RES_10
+- PT_UNICODE
+"6850":
+- PR_FREEBUSY_ALL_EVENTS
+- PT_MV_BINARY
+0e0f:
+- PR_RESPONSIBILITY
+- PT_BOOLEAN
+"0021":
+- PR_ORIGINAL_EITS
+- PT_BINARY
+"3616":
+- PR_DEFAULT_VIEW_ENTRYID
+- PT_BINARY
+"6851":
+- PR_FREEBUSY_TENTATIVE_MONTHS
+- PT_MV_LONG
+"3617":
+- PR_ASSOC_CONTENT_COUNT
+- PT_LONG
+"3880":
+- PR_SYNCEVENT_SUPPRESS_GUID
+- PT_BINARY
+"6720":
+- PR_INTERNET_FREE_DOC_INFO
+- PT_BINARY
+"6852":
+- PR_FREEBUSY_TENTATIVE_EVENTS
+- PT_MV_BINARY
+"0022":
+- PR_ORIGINATOR_CERTIFICATE
+- PT_BINARY
+"0023":
+- PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED
+- PT_BOOLEAN
+809a:
+- PR_EMS_AB_DXA_SVR_SEQ
+- PT_TSTRING
+39fe:
+- PR_SMTP_ADDRESS
+- PT_UNICODE
+"6721":
+- PR_PF_OVER_HARD_QUOTA_LIMIT
+- PT_LONG
+"6853":
+- PR_FREEBUSY_BUSY_MONTHS
+- PT_MV_LONG
+"0024":
+- PR_ORIGINATOR_RETURN_ADDRESS
+- PT_BINARY
+809b:
+- PR_EMS_AB_DXA_SVR_SEQ_TIME
+- PT_SYSTIME
+"6722":
+- PR_PF_MSG_SIZE_LIMIT
+- PT_LONG
+"6854":
+- PR_FREEBUSY_BUSY_EVENTS
+- PT_MV_BINARY
+39ff:
+- PR_7BIT_DISPLAY_NAME
+- PT_TSTRING
+"0025":
+- PR_PARENT_KEY
+- PT_BINARY
+809c:
+- PR_EMS_AB_DXA_SVR_SEQ_USN
+- PT_LONG
+"6855":
+- PR_FREEBUSY_OOF_MONTHS
+- PT_MV_LONG
+"0026":
+- PR_PRIORITY
+- PT_LONG
+809d:
+- PR_EMS_AB_DXA_TASK
+- PT_LONG
+3f8d:
+- PR_PKM_DOC_STATUS
+- PT_UNICODE
+"6856":
+- PR_FREEBUSY_OOF_EVENTS
+- PT_MV_BINARY
+0e1a:
+- PR_MODIFY_VERSION
+- PT_I8
+"0027":
+- PR_ORIGIN_CHECK
+- PT_BINARY
+809e:
+- PR_EMS_AB_DXA_TEMPLATE_OPTIONS
+- PT_LONG
+fff8:
+- PR_EMS_AB_CHILD_RDNS
+- PT_MV_TSTRING
+0e1b:
+- PR_HASATTACH
+- PT_BOOLEAN
+0028:
+- PR_PROOF_OF_SUBMISSION_REQUESTED
+- PT_BOOLEAN
+809f:
+- PR_EMS_AB_DXA_TEMPLATE_TIMESTAMP
+- PT_SYSTIME
+fff9:
+- PR_EMS_AB_HIERARCHY_PATH
+- PT_TSTRING
+3f8e:
+- PR_MV_PKM_OPERATION_REQ
+- PT_MV_UNICODE
+0e1c:
+- PR_BODY_CRC
+- PT_LONG
+0029:
+- PR_READ_RECEIPT_REQUESTED
+- PT_BOOLEAN
+3f8f:
+- PR_PKM_DOC_INTERNAL_STATE
+- PT_UNICODE
+0e1d:
+- PR_NORMALIZED_SUBJECT
+- PT_TSTRING
+"0030":
+- PR_REPLY_TIME
+- PT_SYSTIME
+8c6a:
+- PR_EMS_AB_TAGGED_X509_CERT
+- PT_MV_BINARY
+0e1f:
+- PR_RTF_IN_SYNC
+- PT_BOOLEAN
+"0031":
+- PR_REPORT_TAG
+- PT_BINARY
+8c6b:
+- PR_EMS_AB_PERSONAL_TITLE
+- PT_TSTRING
+0e58:
+- PR_CREATOR_SID
+- PT_BINARY
+"0032":
+- PR_REPORT_TIME
+- PT_SYSTIME
+8c6c:
+- PR_EMS_AB_LANGUAGE_ISO639
+- PT_TSTRING
+0e59:
+- PR_LAST_MODIFIER_SID
+- PT_BINARY
+"0033":
+- PR_RETURNED_IPM
+- PT_BOOLEAN
+"0034":
+- PR_SECURITY
+- PT_LONG
+"0035":
+- PR_INCOMPLETE_COPY
+- PT_BOOLEAN
+0e61:
+- PR_URL_COMP_NAME_POSTFIX
+- PT_LONG
+"0036":
+- PR_SENSITIVITY
+- PT_LONG
+"6602":
+- PR_PROFILE_HOME_SERVER
+- PT_TSTRING
+0e62:
+- PR_URL_COMP_NAME_SET
+- PT_BOOLEAN
+"0037":
+- PR_SUBJECT
+- PT_TSTRING
+0e63:
+- PR_SUBFOLDER_CT
+- PT_LONG
+0038:
+- PR_SUBJECT_IPM
+- PT_BINARY
+0c00:
+- PR_CONTENT_INTEGRITY_CHECK
+- PT_BINARY
+0e64:
+- PR_DELETED_SUBFOLDER_CT
+- PT_LONG
+"6868":
+- PR_FREEBUSY_LAST_MODIFIED
+- PT_SYSTIME
+0039:
+- PR_CLIENT_SUBMIT_TIME
+- PT_SYSTIME
+0c01:
+- PR_EXPLICIT_CONVERSION
+- PT_LONG
+"6869":
+- PR_FREEBUSY_NUM_MONTHS
+- PT_LONG
+0c02:
+- PR_IPM_RETURN_REQUESTED
+- PT_BOOLEAN
+0e66:
+- PR_DELETE_TIME
+- PT_SYSTIME
+"0040":
+- PR_RECEIVED_BY_NAME
+- PT_TSTRING
+"6607":
+- PR_PROFILE_UNRESOLVED_NAME
+- PT_TSTRING
+0c03:
+- PR_MESSAGE_TOKEN
+- PT_BINARY
+0e67:
+- PR_AGE_LIMIT
+- PT_BINARY
+"0041":
+- PR_SENT_REPRESENTING_ENTRYID
+- PT_BINARY
+"6608":
+- PR_PROFILE_UNRESOLVED_SERVER
+- PT_TSTRING
+0c04:
+- PR_NDR_REASON_CODE
+- PT_LONG
+"0042":
+- PR_SENT_REPRESENTING_NAME
+- PT_TSTRING
+0c05:
+- PR_NDR_DIAG_CODE
+- PT_LONG
+000a:
+- PR_CONTENT_RETURN_REQUESTED
+- PT_BOOLEAN
+"0043":
+- PR_RCVD_REPRESENTING_ENTRYID
+- PT_BINARY
+0c06:
+- PR_NON_RECEIPT_NOTIFICATION_REQUESTED
+- PT_BOOLEAN
+"6610":
+- PR_PROFILE_OFFLINE_STORE_PATH
+- PT_TSTRING
+000b:
+- PR_CONVERSATION_KEY
+- PT_BINARY
+0c07:
+- PR_DELIVERY_POINT
+- PT_LONG
+000c:
+- PR_CONVERSION_EITS
+- PT_BINARY
+"0044":
+- PR_RCVD_REPRESENTING_NAME
+- PT_TSTRING
+0c08:
+- PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED
+- PT_BOOLEAN
+670a:
+- PR_LOCAL_COMMIT_TIME_MAX
+- PT_SYSTIME
+"6611":
+- PR_PROFILE_OFFLINE_INFO
+- PT_BINARY
+000d:
+- PR_CONVERSION_WITH_LOSS_PROHIBITED
+- PT_BOOLEAN
+"0045":
+- PR_REPORT_ENTRYID
+- PT_BINARY
+0c09:
+- PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT
+- PT_BINARY
+670b:
+- PR_DELETED_COUNT_TOTAL
+- PT_LONG
+"6743":
+- PR_CONNECTION_MODULUS
+- PT_LONG
+"6612":
+- PR_PROFILE_HOME_SERVER_DN
+- PT_TSTRING
+000e:
+- PR_CONVERTED_EITS
+- PT_BINARY
+"0046":
+- PR_READ_RECEIPT_ENTRYID
+- PT_BINARY
+670c:
+- PR_AUTO_RESET
+- PT_CLSID
+"6744":
+- PR_DELIVER_TO_DN
+- PT_UNICODE
+"6613":
+- PR_PROFILE_HOME_SERVER_ADDRS
+- PT_MV_TSTRING
+000f:
+- PR_DEFERRED_DELIVERY_TIME
+- PT_SYSTIME
+"0047":
+- PR_MESSAGE_SUBMISSION_ID
+- PT_BINARY
+"6614":
+- PR_PROFILE_SERVER_DN
+- PT_TSTRING
+0c10:
+- PR_PHYSICAL_RENDITION_ATTRIBUTES
+- PT_BINARY
+0048:
+- PR_PROVIDER_SUBMIT_TIME
+- PT_SYSTIME
+360a:
+- PR_SUBFOLDERS
+- PT_BOOLEAN
+"6746":
+- PR_MIME_SIZE
+- PT_LONG
+"6615":
+- PR_PROFILE_FAVFLD_COMMENT
+- PT_TSTRING
+0c11:
+- PR_PROOF_OF_DELIVERY
+- PT_BINARY
+0049:
+- PR_ORIGINAL_SUBJECT
+- PT_TSTRING
+360b:
+- PR_STATUS
+- PT_LONG
+"6747":
+- PR_FILE_SIZE
+- PT_I8
+"6616":
+- PR_PROFILE_ALLPUB_DISPLAY_NAME
+- PT_TSTRING
+0c12:
+- PR_PROOF_OF_DELIVERY_REQUESTED
+- PT_BOOLEAN
+360c:
+- PR_ANR
+- PT_TSTRING
+"6748":
+- PR_FID
+- PT_I8
+"0050":
+- PR_REPLY_RECIPIENT_NAMES
+- PT_TSTRING
+"6617":
+- PR_PROFILE_ALLPUB_COMMENT
+- PT_TSTRING
+0c13:
+- PR_RECIPIENT_CERTIFICATE
+- PT_BINARY
+360d:
+- PR_CONTENTS_SORT_ORDER
+- PT_MV_LONG
+"6749":
+- PR_PARENT_FID
+- PT_I8
+"0051":
+- PR_RECEIVED_BY_SEARCH_KEY
+- PT_BINARY
+0c14:
+- PR_RECIPIENT_NUMBER_FOR_ADVICE
+- PT_TSTRING
+66a0:
+- PR_NT_USER_NAME
+- PT_TSTRING
+360e:
+- PR_CONTAINER_HIERARCHY
+- PT_OBJECT
+"0052":
+- PR_RCVD_REPRESENTING_SEARCH_KEY
+- PT_BINARY
+0ffa:
+- PR_STORE_RECORD_KEY
+- PT_BINARY
+0c15:
+- PR_RECIPIENT_TYPE
+- PT_LONG
+66a1:
+- PR_LOCALE_ID
+- PT_LONG
+360f:
+- PR_CONTAINER_CONTENTS
+- PT_OBJECT
+0e79:
+- PR_TRUST_SENDER
+- PT_LONG
+"6750":
+- PR_ICS_NOTIF
+- PT_LONG
+"0053":
+- PR_READ_RECEIPT_SEARCH_KEY
+- PT_BINARY
+0ffb:
+- PR_STORE_ENTRYID
+- PT_BINARY
+0c16:
+- PR_REGISTERED_MAIL_TYPE
+- PT_LONG
+66a2:
+- PR_LAST_LOGON_TIME
+- PT_SYSTIME
+"1100":
+- PR_P1_CONTENT
+- PT_BINARY
+001a:
+- PR_MESSAGE_CLASS
+- PT_TSTRING
+36d0:
+- PR_IPM_APPOINTMENT_ENTRYID
+- PT_BINARY
+"6751":
+- PR_ARTICLE_NUM_NEXT
+- PT_LONG
+"0054":
+- PR_REPORT_SEARCH_KEY
+- PT_BINARY
+0ffc:
+- PR_MINI_ICON
+- PT_BINARY
+0c17:
+- PR_REPLY_REQUESTED
+- PT_BOOLEAN
+66a3:
+- PR_LAST_LOGOFF_TIME
+- PT_SYSTIME
+"1101":
+- PR_P1_CONTENT_TYPE
+- PT_BINARY
+001b:
+- PR_MESSAGE_DELIVERY_ID
+- PT_BINARY
+36d1:
+- PR_IPM_CONTACT_ENTRYID
+- PT_BINARY
+"6752":
+- PR_IMAP_LAST_ARTICLE_ID
+- PT_LONG
+0ffd:
+- PR_ICON
+- PT_BINARY
+0c18:
+- PR_REQUESTED_DELIVERY_METHOD
+- PT_LONG
+66a4:
+- PR_STORAGE_LIMIT_INFORMATION
+- PT_LONG
+36d2:
+- PR_IPM_JOURNAL_ENTRYID
+- PT_BINARY
+671a:
+- PR_MSG_FOLDER_TEMPLATE_RES_11
+- PT_UNICODE
+"6753":
+- PR_NOT_822_RENDERABLE
+- PT_BOOLEAN
+"0055":
+- PR_ORIGINAL_DELIVERY_TIME
+- PT_SYSTIME
+0ffe:
+- PR_OBJECT_TYPE
+- PT_LONG
+0c19:
+- PR_SENDER_ENTRYID
+- PT_BINARY
+66a5:
+- PR_NEWSGROUP_COMPONENT
+- PT_TSTRING
+36d3:
+- PR_IPM_NOTE_ENTRYID
+- PT_BINARY
+671b:
+- PR_MSG_FOLDER_TEMPLATE_RES_12
+- PT_UNICODE
+"0056":
+- PR_ORIGINAL_AUTHOR_SEARCH_KEY
+- PT_BINARY
+66a6:
+- PR_NEWSFEED_INFO
+- PT_BINARY
+"1000":
+- PR_BODY
+- PT_TSTRING
+001e:
+- PR_MESSAGE_SECURITY_LABEL
+- PT_BINARY
+36d4:
+- PR_IPM_TASK_ENTRYID
+- PT_BINARY
+0fff:
+- PR_ENTRYID
+- PT_BINARY
+"0057":
+- PR_MESSAGE_TO_ME
+- PT_BOOLEAN
+66a7:
+- PR_INTERNET_NEWSGROUP_NAME
+- PT_TSTRING
+001f:
+- PR_OBSOLETED_IPMS
+- PT_BINARY
+36d5:
+- PR_REMINDERS_ONLINE_ENTRYID
+- PT_BINARY
+684f:
+- PR_FREEBUSY_ALL_MONTHS
+- PT_MV_LONG
+0058:
+- PR_MESSAGE_CC_ME
+- PT_BOOLEAN
+66a8:
+- PR_FOLDER_FLAGS
+- PT_LONG
+"1001":
+- PR_REPORT_TEXT
+- PT_TSTRING
+36d6:
+- PR_REMINDERS_OFFLINE_ENTRYID
+- PT_BINARY
+671e:
+- PR_PF_PLATINUM_HOME_MDB
+- PT_BOOLEAN
+0059:
+- PR_MESSAGE_RECIP_ME
+- PT_BOOLEAN
+66a9:
+- PR_LAST_ACCESS_TIME
+- PT_SYSTIME
+"1002":
+- PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY
+- PT_BINARY
+36d7:
+- PR_IPM_DRAFTS_ENTRYID
+- PT_BINARY
+671f:
+- PR_PF_PROXY_REQUIRED
+- PT_BOOLEAN
+"6626":
+- PR_ADDRBOOK_FOR_LOCAL_SITE_ENTRYID
+- PT_BINARY
+"1003":
+- PR_REPORTING_DL_NAME
+- PT_BINARY
+361c:
+- PR_PACKED_NAME_PROPS
+- PT_BINARY
+36d8:
+- PR_OUTLOOK_2003_ENTRYIDS
+- PT_MV_BINARY
+"6758":
+- PR_LTID
+- PT_BINARY
+"0060":
+- PR_START_DATE
+- PT_SYSTIME
+"6627":
+- PR_OFFLINE_MESSAGE_ENTRYID
+- PT_BINARY
+3a00:
+- PR_ACCOUNT
+- PT_TSTRING
+"1004":
+- PR_REPORTING_MTA_CERTIFICATE
+- PT_BINARY
+"6759":
+- PR_CN_EXPORT
+- PT_BINARY
diff --git a/vendor/ruby-msg/data/named_map.yaml b/vendor/ruby-msg/data/named_map.yaml
new file mode 100644
index 000000000..8de27d13f
--- /dev/null
+++ b/vendor/ruby-msg/data/named_map.yaml
@@ -0,0 +1,114 @@
+# this file provides for the mapping of the keys of named properties
+# to symbolic names (as opposed to mapitags.yaml, which is currently
+# in a different format, has a different source, and is only fixed
+# code properties)
+#
+# essentially the symbols are slightly munged versions of the names
+# given to these properties by CDO, or Outlook's object model.
+# it was parsed out of cdo10.htm, and neatened up a bit.
+#
+# interestingly, despite having separate guids, the codes are picked not to
+# clash. further the names themselves have only 3 clashes in all the below.
+{
+[0x8005, PSETID_Address]: file_under,
+[0x8017, PSETID_Address]: last_name_and_first_name,
+[0x8018, PSETID_Address]: company_and_full_name,
+[0x8019, PSETID_Address]: full_name_and_company,
+[0x801a, PSETID_Address]: home_address,
+[0x801b, PSETID_Address]: business_address,
+[0x801c, PSETID_Address]: other_address,
+[0x8022, PSETID_Address]: selected_address,
+[0x802b, PSETID_Address]: web_page,
+[0x802c, PSETID_Address]: yomi_first_name,
+[0x802d, PSETID_Address]: yomi_last_name,
+[0x802e, PSETID_Address]: yomi_company_name,
+[0x8030, PSETID_Address]: last_first_no_space,
+[0x8031, PSETID_Address]: last_first_space_only,
+[0x8032, PSETID_Address]: company_last_first_no_space,
+[0x8033, PSETID_Address]: company_last_first_space_only,
+[0x8034, PSETID_Address]: last_first_no_space_company,
+[0x8035, PSETID_Address]: last_first_space_only_company,
+[0x8036, PSETID_Address]: last_first_and_suffix,
+[0x8045, PSETID_Address]: business_address_street,
+[0x8046, PSETID_Address]: business_address_city,
+[0x8047, PSETID_Address]: business_address_state,
+[0x8048, PSETID_Address]: business_address_postal_code,
+[0x8049, PSETID_Address]: business_address_country,
+[0x804a, PSETID_Address]: business_address_post_office_box,
+[0x804f, PSETID_Address]: user_field1,
+[0x8050, PSETID_Address]: user_field2,
+[0x8051, PSETID_Address]: user_field3,
+[0x8052, PSETID_Address]: user_field4,
+[0x8062, PSETID_Address]: imaddress,
+[0x8082, PSETID_Address]: email_addr_type,
+[0x8083, PSETID_Address]: email_email_address,
+[0x8084, PSETID_Address]: email_original_display_name,
+[0x8085, PSETID_Address]: email_original_entry_id,
+[0x8092, PSETID_Address]: email2_addr_type,
+[0x8093, PSETID_Address]: email2_email_address,
+[0x8094, PSETID_Address]: email2_original_display_name,
+[0x8095, PSETID_Address]: email2_original_entry_id,
+[0x80a2, PSETID_Address]: email3_addr_type,
+[0x80a3, PSETID_Address]: email3_email_address,
+[0x80a4, PSETID_Address]: email3_original_display_name,
+[0x80a5, PSETID_Address]: email3_original_entry_id,
+[0x80d8, PSETID_Address]: internet_free_busy_address,
+[0x8101, PSETID_Task]: status,
+[0x8102, PSETID_Task]: percent_complete,
+[0x8103, PSETID_Task]: team_task,
+[0x8104, PSETID_Task]: start_date,
+[0x8105, PSETID_Task]: due_date,
+[0x8106, PSETID_Task]: duration,
+[0x810f, PSETID_Task]: date_completed,
+[0x8110, PSETID_Task]: actual_work,
+[0x8111, PSETID_Task]: total_work,
+[0x811c, PSETID_Task]: complete,
+[0x811f, PSETID_Task]: owner,
+[0x8126, PSETID_Task]: is_recurring,
+[0x8205, PSETID_Appointment]: busy_status,
+[0x8208, PSETID_Appointment]: location,
+[0x820d, PSETID_Appointment]: start_date,
+[0x820e, PSETID_Appointment]: end_date,
+[0x8213, PSETID_Appointment]: duration,
+[0x8214, PSETID_Appointment]: colors,
+[0x8216, PSETID_Appointment]: recurrence_state,
+[0x8218, PSETID_Appointment]: response_status,
+[0x8222, PSETID_Appointment]: reply_time,
+[0x8223, PSETID_Appointment]: is_recurring,
+[0x822e, PSETID_Appointment]: organizer,
+[0x8231, PSETID_Appointment]: recurrence_type,
+[0x8232, PSETID_Appointment]: recurrence_pattern,
+# also had CdoPR_FLAG_DUE_BY, when applied to messages. i don't currently
+# use message class specific names
+[0x8502, PSETID_Common]: reminder_time,
+[0x8503, PSETID_Common]: reminder_set,
+[0x8516, PSETID_Common]: common_start,
+[0x8517, PSETID_Common]: common_end,
+[0x851c, PSETID_Common]: reminder_override,
+[0x851e, PSETID_Common]: reminder_sound,
+[0x851f, PSETID_Common]: reminder_file,
+# this one only listed as CdoPR_FLAG_TEXT. maybe should be
+# reminder_text
+[0x8530, PSETID_Common]: flag_text,
+[0x8534, PSETID_Common]: mileage,
+[0x8535, PSETID_Common]: billing_information,
+[0x8539, PSETID_Common]: companies,
+[0x853a, PSETID_Common]: contact_names,
+# had CdoPR_FLAG_DUE_BY_NEXT for this one also
+[0x8560, PSETID_Common]: reminder_next_time,
+[0x8700, PSETID_Log]: entry,
+[0x8704, PSETID_Log]: start_date,
+[0x8705, PSETID_Log]: start_time,
+[0x8706, PSETID_Log]: start,
+[0x8707, PSETID_Log]: duration,
+[0x8708, PSETID_Log]: end,
+[0x870e, PSETID_Log]: doc_printed,
+[0x870f, PSETID_Log]: doc_saved,
+[0x8710, PSETID_Log]: doc_routed,
+[0x8711, PSETID_Log]: doc_posted,
+[0x8712, PSETID_Log]: entry_type,
+[0x8b00, PSETID_Note]: color,
+[0x8b02, PSETID_Note]: width,
+[0x8b03, PSETID_Note]: height,
+["Keywords", PS_PUBLIC_STRINGS]: categories
+}
diff --git a/vendor/ruby-msg/data/types.yaml b/vendor/ruby-msg/data/types.yaml
new file mode 100644
index 000000000..b2c9024b8
--- /dev/null
+++ b/vendor/ruby-msg/data/types.yaml
@@ -0,0 +1,15 @@
+---
+# grep ' PT_' mapitags.yaml | sort -u > types.yaml
+- PT_BINARY
+- PT_BOOLEAN
+- PT_CLSID
+- PT_I8
+- PT_LONG
+- PT_MV_BINARY
+- PT_MV_LONG
+- PT_MV_TSTRING
+- PT_OBJECT
+- PT_SHORT
+- PT_STRING8
+- PT_SYSTIME
+- PT_TSTRING
diff --git a/vendor/ruby-msg/lib/mapi.rb b/vendor/ruby-msg/lib/mapi.rb
new file mode 100644
index 000000000..b9d3413f7
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi.rb
@@ -0,0 +1,109 @@
+require 'mapi/types'
+require 'mapi/property_set'
+
+module Mapi
+ VERSION = '1.4.0'
+
+ #
+ # Mapi::Item is the base class used for all mapi objects, and is purely a
+ # property set container
+ #
+ class Item
+ attr_reader :properties
+ alias props properties
+
+ # +properties+ should be a PropertySet instance.
+ def initialize properties
+ @properties = properties
+ end
+ end
+
+ # a general attachment class. is subclassed by Msg and Pst attachment classes
+ class Attachment < Item
+ def filename
+ props.attach_long_filename || props.attach_filename
+ end
+
+ def data
+ @embedded_msg || @embedded_ole || props.attach_data
+ end
+
+ # with new stream work, its possible to not have the whole thing in memory at one time,
+ # just to save an attachment
+ #
+ # a = msg.attachments.first
+ # a.save open(File.basename(a.filename || 'attachment'), 'wb')
+ def save io
+ raise "can only save binary data blobs, not ole dirs" if @embedded_ole
+ data.each_read { |chunk| io << chunk }
+ end
+
+ def inspect
+ "#<#{self.class.to_s[/\w+$/]}" +
+ (filename ? " filename=#{filename.inspect}" : '') +
+ (@embedded_ole ? " embedded_type=#{@embedded_ole.embedded_type.inspect}" : '') + ">"
+ end
+ end
+
+ class Recipient < Item
+ # some kind of best effort guess for converting to standard mime style format.
+ # there are some rules for encoding non 7bit stuff in mail headers. should obey
+ # that here, as these strings could be unicode
+ # email_address will be an EX:/ address (X.400?), unless external recipient. the
+ # other two we try first.
+ # consider using entry id for this too.
+ def name
+ name = props.transmittable_display_name || props.display_name
+ # dequote
+ name[/^'(.*)'/, 1] or name rescue nil
+ end
+
+ def email
+ props.smtp_address || props.org_email_addr || props.email_address
+ end
+
+ RECIPIENT_TYPES = { 0 => :orig, 1 => :to, 2 => :cc, 3 => :bcc }
+ def type
+ RECIPIENT_TYPES[props.recipient_type]
+ end
+
+ def to_s
+ if name = self.name and !name.empty? and email && name != email
+ %{"#{name}" <#{email}>}
+ else
+ email || name
+ end
+ end
+
+ def inspect
+ "#<#{self.class.to_s[/\w+$/]}:#{self.to_s.inspect}>"
+ end
+ end
+
+ # i refer to it as a message (as does mapi), although perhaps Item is better, as its a more general
+ # concept than a message, as used in Pst files. though maybe i'll switch to using
+ # Mapi::Object as the base class there.
+ #
+ # IMessage essentially, but there's also stuff like IMAPIFolder etc. so, for this to form
+ # basis for PST Item, it'd need to be more general.
+ class Message < Item
+ # these 2 collections should be provided by our subclasses
+ def attachments
+ raise NotImplementedError
+ end
+
+ def recipients
+ raise NotImplementedError
+ end
+
+ def inspect
+ str = %w[message_class from to subject].map do |key|
+ " #{key}=#{props.send(key).inspect}"
+ end.compact.join
+ str << " recipients=#{recipients.inspect}"
+ str << " attachments=#{attachments.inspect}"
+ "#<#{self.class.to_s[/\w+$/]}#{str}>"
+ end
+ end
+end
+
diff --git a/vendor/ruby-msg/lib/mapi/convert.rb b/vendor/ruby-msg/lib/mapi/convert.rb
new file mode 100644
index 000000000..4c7a0d298
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi/convert.rb
@@ -0,0 +1,61 @@
+# we have two different "backends" for note conversion. we're sticking with
+# the current (home grown) mime one until the tmail version is suitably
+# polished.
+require 'mapi/convert/note-mime'
+require 'mapi/convert/contact'
+
+module Mapi
+ class Message
+ CONVERSION_MAP = {
+ 'text/x-vcard' => [:to_vcard, 'vcf'],
+ 'message/rfc822' => [:to_mime, 'eml'],
+ 'text/plain' => [:to_post, 'txt']
+ # ...
+ }
+
+ # get the mime type of the message.
+ def mime_type
+ case props.message_class #.downcase <- have a feeling i saw other cased versions
+ when 'IPM.Contact'
+ # apparently "text/directory; profile=vcard" is what you're supposed to use
+ 'text/x-vcard'
+ when 'IPM.Note'
+ 'message/rfc822'
+ when 'IPM.Post'
+ 'text/plain'
+ when 'IPM.StickyNote'
+ 'text/plain' # hmmm....
+ else
+ Mapi::Log.warn 'unknown message_class - %p' % props.message_class
+ nil
+ end
+ end
+
+ def convert
+ type = mime_type
+ unless pair = CONVERSION_MAP[type]
+ raise 'unable to convert message with mime type - %p' % type
+ end
+ send pair.first
+ end
+
+ # should probably be moved to mapi/convert/post
+ class Post
+ # not really sure what the pertinent properties are. we just do nothing for now...
+ def initialize message
+ @message = message
+ end
+
+ def to_s
+ # should maybe handle other types, like html body. need a better format for post
+ # probably anyway, cause a lot of meta data is getting chucked.
+ @message.props.body
+ end
+ end
+
+ def to_post
+ Post.new self
+ end
+ end
+end
+
diff --git a/vendor/ruby-msg/lib/mapi/convert/contact.rb b/vendor/ruby-msg/lib/mapi/convert/contact.rb
new file mode 100644
index 000000000..838ae6498
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi/convert/contact.rb
@@ -0,0 +1,142 @@
+require 'rubygems'
+require 'vpim/vcard'
+
+# patch Vpim. TODO - fix upstream, or verify old behaviour was ok
+def Vpim.encode_text v
+ # think the regexp was wrong
+ v.to_str.gsub(/(.)/m) do
+ case $1
+ when "\n"
+ "\\n"
+ when "\\", ",", ";"
+ "\\#{$1}"
+ else
+ $1
+ end
+ end
+end
+
+module Mapi
+ class Message
+ class VcardConverter
+ include Vpim
+
+ # a very incomplete mapping, but its a start...
+ # can't find where to set a lot of stuff, like zipcode, jobtitle etc
+ VCARD_MAP = {
+ # these are all standard mapi properties
+ :name => [
+ {
+ :given => :given_name,
+ :family => :surname,
+ :fullname => :subject
+ }
+ ],
+ # outlook seems to eschew the mapi properties this time,
+ # like postal_address, street_address, home_address_city
+ # so we use the named properties
+ :addr => [
+ {
+ :location => 'work',
+ :street => :business_address_street,
+ :locality => proc do |props|
+ [props.business_address_city, props.business_address_state].compact * ', '
+ end
+ }
+ ],
+
+ # right type? maybe date
+ :birthday => :birthday,
+ :nickname => :nickname
+
+ # photo available?
+ # FIXME finish, emails, telephones etc
+ }
+
+ attr_reader :msg
+ def initialize msg
+ @msg = msg
+ end
+
+ def field name, *args
+ DirectoryInfo::Field.create name, Vpim.encode_text_list(args)
+ end
+
+ def get_property key
+ if String === key
+ return key
+ elsif key.respond_to? :call
+ value = key.call msg.props
+ else
+ value = msg.props[key]
+ end
+ if String === value and value.empty?
+ nil
+ else
+ value
+ end
+ end
+
+ def get_properties hash
+ constants = {}
+ others = {}
+ hash.each do |to, from|
+ if String === from
+ constants[to] = from
+ else
+ value = get_property from
+ others[to] = value if value
+ end
+ end
+ return nil if others.empty?
+ others.merge constants
+ end
+
+ def convert
+ Vpim::Vcard::Maker.make2 do |m|
+ # handle name
+ [:name, :addr].each do |type|
+ VCARD_MAP[type].each do |hash|
+ next unless props = get_properties(hash)
+ m.send "add_#{type}" do |n|
+ props.each { |key, value| n.send "#{key}=", value }
+ end
+ end
+ end
+
+ (VCARD_MAP.keys - [:name, :addr]).each do |key|
+ value = get_property VCARD_MAP[key]
+ m.send "#{key}=", value if value
+ end
+
+ # the rest of the stuff is custom
+
+ url = get_property(:webpage) || get_property(:business_home_page)
+ m.add_field field('URL', url) if url
+ m.add_field field('X-EVOLUTION-FILE-AS', get_property(:file_under)) if get_property(:file_under)
+
+ addr = get_property(:email_email_address) || get_property(:email_original_display_name)
+ if addr
+ m.add_email addr do |e|
+ e.format ='x400' unless msg.props.email_addr_type == 'SMTP'
+ end
+ end
+
+ if org = get_property(:company_name)
+ m.add_field field('ORG', get_property(:company_name))
+ end
+
+ # TODO: imaddress
+ end
+ end
+ end
+
+ def to_vcard
+ #p props.raw.reject { |key, value| key.guid.inspect !~ /00062004-0000-0000-c000-000000000046/ }.
+ # map { |key, value| [key.to_sym, value] }.reject { |a, b| b.respond_to? :read }
+ #y props.to_h.reject { |a, b| b.respond_to? :read }
+ VcardConverter.new(self).convert
+ end
+ end
+end
+
diff --git a/vendor/ruby-msg/lib/mapi/convert/note-mime.rb b/vendor/ruby-msg/lib/mapi/convert/note-mime.rb
new file mode 100644
index 000000000..deb035f2c
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi/convert/note-mime.rb
@@ -0,0 +1,274 @@
+require 'base64'
+require 'mime'
+require 'time'
+
+# there is still some Msg specific stuff in here.
+
+module Mapi
+ class Message
+ def mime
+ return @mime if @mime
+ # if these headers exist at all, they can be helpful. we may however get a
+ # application/ms-tnef mime root, which means there will be little other than
+ # headers. we may get nothing.
+ # and other times, when received from external, we get the full cigar, boundaries
+ # etc and all.
+ # sometimes its multipart, with no boundaries. that throws an error. so we'll be more
+ # forgiving here
+ @mime = SimpleMime.new props.transport_message_headers.to_s, true
+ populate_headers
+ @mime
+ end
+
+ def headers
+ mime.headers
+ end
+
+ # copy data from msg properties storage to standard mime. headers
+ # i've now seen it where the existing headers had heaps on stuff, and the msg#props had
+ # practically nothing. think it was because it was a tnef - msg conversion done by exchange.
+ def populate_headers
+ # construct a From value
+ # should this kind of thing only be done when headers don't exist already? maybe not. if its
+ # sent, then modified and saved, the headers could be wrong?
+ # hmmm. i just had an example where a mail is sent, from an internal user, but it has transport
+ # headers, i think because one recipient was external. the only place the senders email address
+ # exists is in the transport headers. so its maybe not good to overwrite from.
+ # recipients however usually have smtp address available.
+ # maybe we'll do it for all addresses that are smtp? (is that equivalent to
+ # sender_email_address !~ /^\//
+ name, email = props.sender_name, props.sender_email_address
+ if props.sender_addrtype == 'SMTP'
+ headers['From'] = if name and email and name != email
+ [%{"#{name}" <#{email}>}]
+ else
+ [email || name]
+ end
+ elsif !headers.has_key?('From')
+ # some messages were never sent, so that sender stuff isn't filled out. need to find another
+ # way to get something
+ # what about marking whether we thing the email was sent or not? or draft?
+ # for partition into an eventual Inbox, Sent, Draft mbox set?
+ # i've now seen cases where this stuff is missing, but exists in transport message headers,
+ # so maybe i should inhibit this in that case.
+ if email
+ # disabling this warning for now
+ #Log.warn "* no smtp sender email address available (only X.400). creating fake one"
+ # this is crap. though i've specially picked the logic so that it generates the correct
+ # email addresses in my case (for my organisation).
+ # this user stuff will give valid email i think, based on alias.
+ user = name ? name.sub(/(.*), (.*)/, "\\2.\\1") : email[/\w+$/].downcase
+ domain = (email[%r{^/O=([^/]+)}i, 1].downcase + '.com' rescue email)
+ headers['From'] = [name ? %{"#{name}" <#{user}@#{domain}>} : "<#{user}@#{domain}>" ]
+ elsif name
+ # we only have a name? thats screwed up.
+ # disabling this warning for now
+ #Log.warn "* no smtp sender email address available (only name). creating fake one"
+ headers['From'] = [%{"#{name}"}]
+ else
+ # disabling this warning for now
+ #Log.warn "* no sender email address available at all. FIXME"
+ end
+ # else we leave the transport message header version
+ end
+
+ # for all of this stuff, i'm assigning in utf8 strings.
+ # thats ok i suppose, maybe i can say its the job of the mime class to handle that.
+ # but a lot of the headers are overloaded in different ways. plain string, many strings
+ # other stuff. what happens to a person who has a " in their name etc etc. encoded words
+ # i suppose. but that then happens before assignment. and can't be automatically undone
+ # until the header is decomposed into recipients.
+ recips_by_type = recipients.group_by { |r| r.type }
+ # i want to the the types in a specific order.
+ [:to, :cc, :bcc].each do |type|
+ # don't know why i bother, but if we can, we try to sort recipients by the numerical part
+ # of the ole name, or just leave it if we can't
+ recips = recips_by_type[type]
+ recips = (recips.sort_by { |r| r.obj.name[/\d{8}$/].hex } rescue recips)
+ # switched to using , for separation, not ;. see issue #4
+ # recips.empty? is strange. i wouldn't have thought it possible, but it was right?
+ headers[type.to_s.sub(/^(.)/) { $1.upcase }] = [recips.join(', ')] if recips and !recips.empty?
+ end
+ headers['Subject'] = [props.subject] if props.subject
+
+ # fill in a date value. by default, we won't mess with existing value hear
+ if !headers.has_key?('Date')
+ # we want to get a received date, as i understand it.
+ # use this preference order, or pull the most recent?
+ keys = %w[message_delivery_time client_submit_time last_modification_time creation_time]
+ time = keys.each { |key| break time if time = props.send(key) }
+ time = nil unless Date === time
+
+ # now convert and store
+ # this is a little funky. not sure about time zone stuff either?
+ # actually seems ok. maybe its always UTC and interpreted anyway. or can be timezoneless.
+ # i have no timezone info anyway.
+ # in gmail, i see stuff like 15 Jan 2007 00:48:19 -0000, and it displays as 11:48.
+ # can also add .localtime here if desired. but that feels wrong.
+ headers['Date'] = [Time.iso8601(time.to_s).rfc2822] if time
+ end
+
+ # some very simplistic mapping between internet message headers and the
+ # mapi properties
+ # any of these could be causing duplicates due to case issues. the hack in #to_mime
+ # just stops re-duplication at that point. need to move some smarts into the mime
+ # code to handle it.
+ mapi_header_map = [
+ [:internet_message_id, 'Message-ID'],
+ [:in_reply_to_id, 'In-Reply-To'],
+ # don't set these values if they're equal to the defaults anyway
+ [:importance, 'Importance', proc { |val| val.to_s == '1' ? nil : val }],
+ [:priority, 'Priority', proc { |val| val.to_s == '1' ? nil : val }],
+ [:sensitivity, 'Sensitivity', proc { |val| val.to_s == '0' ? nil : val }],
+ # yeah?
+ [:conversation_topic, 'Thread-Topic'],
+ # not sure of the distinction here
+ # :originator_delivery_report_requested ??
+ [:read_receipt_requested, 'Disposition-Notification-To', proc { |val| from }]
+ ]
+ mapi_header_map.each do |mapi, mime, *f|
+ next unless q = val = props.send(mapi) or headers.has_key?(mime)
+ next if f[0] and !(val = f[0].call(val))
+ headers[mime] = [val.to_s]
+ end
+ end
+
+ # redundant?
+ def type
+ props.message_class[/IPM\.(.*)/, 1].downcase rescue nil
+ end
+
+ # shortcuts to some things from the headers
+ %w[From To Cc Bcc Subject].each do |key|
+ define_method(key.downcase) { headers[key].join(' ') if headers.has_key?(key) }
+ end
+
+ def body_to_mime
+ # to create the body
+ # should have some options about serializing rtf. and possibly options to check the rtf
+ # for rtf2html conversion, stripping those html tags or other similar stuff. maybe want to
+ # ignore it in the cases where it is generated from incoming html. but keep it if it was the
+ # source for html and plaintext.
+ if props.body_rtf or props.body_html
+ # should plain come first?
+ mime = SimpleMime.new "Content-Type: multipart/alternative\r\n\r\n"
+ # its actually possible for plain body to be empty, but the others not.
+ # if i can get an html version, then maybe a callout to lynx can be made...
+ mime.parts << SimpleMime.new("Content-Type: text/plain\r\n\r\n" + props.body) if props.body
+ # this may be automatically unwrapped from the rtf if the rtf includes the html
+ mime.parts << SimpleMime.new("Content-Type: text/html\r\n\r\n" + props.body_html) if props.body_html
+ # temporarily disabled the rtf. its just showing up as an attachment anyway.
+ #mime.parts << SimpleMime.new("Content-Type: text/rtf\r\n\r\n" + props.body_rtf) if props.body_rtf
+ # its thus currently possible to get no body at all if the only body is rtf. that is not
+ # really acceptable FIXME
+ mime
+ else
+ # check no header case. content type? etc?. not sure if my SimpleMime class will accept
+ Log.debug "taking that other path"
+ # body can be nil, hence the to_s
+ SimpleMime.new "Content-Type: text/plain\r\n\r\n" + props.body.to_s
+ end
+ end
+
+ def to_mime
+ # intended to be used for IPM.note, which is the email type. can use it for others if desired,
+ # YMMV
+ Log.warn "to_mime used on a #{props.message_class}" unless props.message_class == 'IPM.Note'
+ # we always have a body
+ mime = body = body_to_mime
+
+ # If we have attachments, we take the current mime root (body), and make it the first child
+ # of a new tree that will contain body and attachments.
+ unless attachments.empty?
+ mime = SimpleMime.new "Content-Type: multipart/mixed\r\n\r\n"
+ mime.parts << body
+ # i don't know any better way to do this. need multipart/related for inline images
+ # referenced by cid: urls to work, but don't want to use it otherwise...
+ related = false
+ attachments.each do |attach|
+ part = attach.to_mime
+ related = true if part.headers.has_key?('Content-ID') or part.headers.has_key?('Content-Location')
+ mime.parts << part
+ end
+ mime.headers['Content-Type'] = ['multipart/related'] if related
+ end
+
+ # at this point, mime is either
+ # - a single text/plain, consisting of the body ('taking that other path' above. rare)
+ # - a multipart/alternative, consiting of a few bodies (plain and html body. common)
+ # - a multipart/mixed, consisting of 1 of the above 2 types of bodies, and attachments.
+ # we add this standard preamble if its multipart
+ # FIXME preamble.replace, and body.replace both suck.
+ # preamble= is doable. body= wasn't being done because body will get rewritten from parts
+ # if multipart, and is only there readonly. can do that, or do a reparse...
+ # The way i do this means that only the first preamble will say it, not preambles of nested
+ # multipart chunks.
+ mime.preamble.replace "This is a multi-part message in MIME format.\r\n" if mime.multipart?
+
+ # now that we have a root, we can mix in all our headers
+ headers.each do |key, vals|
+ # don't overwrite the content-type, encoding style stuff
+ next if mime.headers.has_key? key
+ # some new temporary hacks
+ next if key =~ /content-type/i and vals[0] =~ /base64/
+ next if mime.headers.keys.map(&:downcase).include? key.downcase
+ mime.headers[key] += vals
+ end
+ # just a stupid hack to make the content-type header last, when using OrderedHash
+ mime.headers['Content-Type'] = mime.headers.delete 'Content-Type'
+
+ mime
+ end
+ end
+
+ class Attachment
+ def to_mime
+ # TODO: smarter mime typing.
+ mimetype = props.attach_mime_tag || 'application/octet-stream'
+ mime = SimpleMime.new "Content-Type: #{mimetype}\r\n\r\n"
+ mime.headers['Content-Disposition'] = [%{attachment; filename="#{filename}"}]
+ mime.headers['Content-Transfer-Encoding'] = ['base64']
+ mime.headers['Content-Location'] = [props.attach_content_location] if props.attach_content_location
+ mime.headers['Content-ID'] = [props.attach_content_id] if props.attach_content_id
+ # data.to_s for now. data was nil for some reason.
+ # perhaps it was a data object not correctly handled?
+ # hmmm, have to use read here. that assumes that the data isa stream.
+ # but if the attachment data is a string, then it won't work. possible?
+ data_str = if @embedded_msg
+ mime.headers['Content-Type'] = 'message/rfc822'
+ # lets try making it not base64 for now
+ mime.headers.delete 'Content-Transfer-Encoding'
+ # not filename. rather name, or something else right?
+ # maybe it should be inline?? i forget attach_method / access meaning
+ mime.headers['Content-Disposition'] = [%{attachment; filename="#{@embedded_msg.subject}"}]
+ @embedded_msg.to_mime.to_s
+ elsif @embedded_ole
+ # kind of hacky
+ io = StringIO.new
+ Ole::Storage.new io do |ole|
+ ole.root.type = :dir
+ Ole::Storage::Dirent.copy @embedded_ole, ole.root
+ end
+ io.string
+ else
+ # FIXME: shouldn't be required
+ data.read.to_s rescue ''
+ end
+ mime.body.replace @embedded_msg ? data_str : Base64.encode64(data_str).gsub(/\n/, "\r\n")
+ mime
+ end
+ end
+
+ class Msg < Message
+ def populate_headers
+ super
+ if !headers.has_key?('Date')
+ # can employ other methods for getting a time. heres one in a similar vein to msgconvert.pl,
+ # ie taking the time from an ole object
+ time = @root.ole.dirents.map { |dirent| dirent.modify_time || dirent.create_time }.compact.sort.last
+ headers['Date'] = [Time.iso8601(time.to_s).rfc2822] if time
+ end
+ end
+ end
+end
+
diff --git a/vendor/ruby-msg/lib/mapi/convert/note-tmail.rb b/vendor/ruby-msg/lib/mapi/convert/note-tmail.rb
new file mode 100644
index 000000000..9ccc9e0b3
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi/convert/note-tmail.rb
@@ -0,0 +1,287 @@
+require 'rubygems'
+require 'tmail'
+
+# these will be removed later
+require 'time'
+require 'mime'
+
+# there is some Msg specific stuff in here.
+
+class TMail::Mail
+ def quoted_body= str
+ body_port.wopen { |f| f.write str }
+ str
+ end
+end
+
+module Mapi
+ class Message
+ def mime
+ return @mime if @mime
+ # if these headers exist at all, they can be helpful. we may however get a
+ # application/ms-tnef mime root, which means there will be little other than
+ # headers. we may get nothing.
+ # and other times, when received from external, we get the full cigar, boundaries
+ # etc and all.
+ # sometimes its multipart, with no boundaries. that throws an error. so we'll be more
+ # forgiving here
+ @mime = Mime.new props.transport_message_headers.to_s, true
+ populate_headers
+ @mime
+ end
+
+ def headers
+ mime.headers
+ end
+
+ # copy data from msg properties storage to standard mime. headers
+ # i've now seen it where the existing headers had heaps on stuff, and the msg#props had
+ # practically nothing. think it was because it was a tnef - msg conversion done by exchange.
+ def populate_headers
+ # construct a From value
+ # should this kind of thing only be done when headers don't exist already? maybe not. if its
+ # sent, then modified and saved, the headers could be wrong?
+ # hmmm. i just had an example where a mail is sent, from an internal user, but it has transport
+ # headers, i think because one recipient was external. the only place the senders email address
+ # exists is in the transport headers. so its maybe not good to overwrite from.
+ # recipients however usually have smtp address available.
+ # maybe we'll do it for all addresses that are smtp? (is that equivalent to
+ # sender_email_address !~ /^\//
+ name, email = props.sender_name, props.sender_email_address
+ if props.sender_addrtype == 'SMTP'
+ headers['From'] = if name and email and name != email
+ [%{"#{name}" <#{email}>}]
+ else
+ [email || name]
+ end
+ elsif !headers.has_key?('From')
+ # some messages were never sent, so that sender stuff isn't filled out. need to find another
+ # way to get something
+ # what about marking whether we thing the email was sent or not? or draft?
+ # for partition into an eventual Inbox, Sent, Draft mbox set?
+ # i've now seen cases where this stuff is missing, but exists in transport message headers,
+ # so maybe i should inhibit this in that case.
+ if email
+ # disabling this warning for now
+ #Log.warn "* no smtp sender email address available (only X.400). creating fake one"
+ # this is crap. though i've specially picked the logic so that it generates the correct
+ # email addresses in my case (for my organisation).
+ # this user stuff will give valid email i think, based on alias.
+ user = name ? name.sub(/(.*), (.*)/, "\\2.\\1") : email[/\w+$/].downcase
+ domain = (email[%r{^/O=([^/]+)}i, 1].downcase + '.com' rescue email)
+ headers['From'] = [name ? %{"#{name}" <#{user}@#{domain}>} : "<#{user}@#{domain}>" ]
+ elsif name
+ # we only have a name? thats screwed up.
+ # disabling this warning for now
+ #Log.warn "* no smtp sender email address available (only name). creating fake one"
+ headers['From'] = [%{"#{name}"}]
+ else
+ # disabling this warning for now
+ #Log.warn "* no sender email address available at all. FIXME"
+ end
+ # else we leave the transport message header version
+ end
+
+ # for all of this stuff, i'm assigning in utf8 strings.
+ # thats ok i suppose, maybe i can say its the job of the mime class to handle that.
+ # but a lot of the headers are overloaded in different ways. plain string, many strings
+ # other stuff. what happens to a person who has a " in their name etc etc. encoded words
+ # i suppose. but that then happens before assignment. and can't be automatically undone
+ # until the header is decomposed into recipients.
+ recips_by_type = recipients.group_by { |r| r.type }
+ # i want to the the types in a specific order.
+ [:to, :cc, :bcc].each do |type|
+ # don't know why i bother, but if we can, we try to sort recipients by the numerical part
+ # of the ole name, or just leave it if we can't
+ recips = recips_by_type[type]
+ recips = (recips.sort_by { |r| r.obj.name[/\d{8}$/].hex } rescue recips)
+ # switched to using , for separation, not ;. see issue #4
+ # recips.empty? is strange. i wouldn't have thought it possible, but it was right?
+ headers[type.to_s.sub(/^(.)/) { $1.upcase }] = [recips.join(', ')] unless recips.empty?
+ end
+ headers['Subject'] = [props.subject] if props.subject
+
+ # fill in a date value. by default, we won't mess with existing value hear
+ if !headers.has_key?('Date')
+ # we want to get a received date, as i understand it.
+ # use this preference order, or pull the most recent?
+ keys = %w[message_delivery_time client_submit_time last_modification_time creation_time]
+ time = keys.each { |key| break time if time = props.send(key) }
+ time = nil unless Date === time
+
+ # now convert and store
+ # this is a little funky. not sure about time zone stuff either?
+ # actually seems ok. maybe its always UTC and interpreted anyway. or can be timezoneless.
+ # i have no timezone info anyway.
+ # in gmail, i see stuff like 15 Jan 2007 00:48:19 -0000, and it displays as 11:48.
+ # can also add .localtime here if desired. but that feels wrong.
+ headers['Date'] = [Time.iso8601(time.to_s).rfc2822] if time
+ end
+
+ # some very simplistic mapping between internet message headers and the
+ # mapi properties
+ # any of these could be causing duplicates due to case issues. the hack in #to_mime
+ # just stops re-duplication at that point. need to move some smarts into the mime
+ # code to handle it.
+ mapi_header_map = [
+ [:internet_message_id, 'Message-ID'],
+ [:in_reply_to_id, 'In-Reply-To'],
+ # don't set these values if they're equal to the defaults anyway
+ [:importance, 'Importance', proc { |val| val.to_s == '1' ? nil : val }],
+ [:priority, 'Priority', proc { |val| val.to_s == '1' ? nil : val }],
+ [:sensitivity, 'Sensitivity', proc { |val| val.to_s == '0' ? nil : val }],
+ # yeah?
+ [:conversation_topic, 'Thread-Topic'],
+ # not sure of the distinction here
+ # :originator_delivery_report_requested ??
+ [:read_receipt_requested, 'Disposition-Notification-To', proc { |val| from }]
+ ]
+ mapi_header_map.each do |mapi, mime, *f|
+ next unless q = val = props.send(mapi) or headers.has_key?(mime)
+ next if f[0] and !(val = f[0].call(val))
+ headers[mime] = [val.to_s]
+ end
+ end
+
+ # redundant?
+ def type
+ props.message_class[/IPM\.(.*)/, 1].downcase rescue nil
+ end
+
+ # shortcuts to some things from the headers
+ %w[From To Cc Bcc Subject].each do |key|
+ define_method(key.downcase) { headers[key].join(' ') if headers.has_key?(key) }
+ end
+
+ def body_to_tmail
+ # to create the body
+ # should have some options about serializing rtf. and possibly options to check the rtf
+ # for rtf2html conversion, stripping those html tags or other similar stuff. maybe want to
+ # ignore it in the cases where it is generated from incoming html. but keep it if it was the
+ # source for html and plaintext.
+ if props.body_rtf or props.body_html
+ # should plain come first?
+ part = TMail::Mail.new
+ # its actually possible for plain body to be empty, but the others not.
+ # if i can get an html version, then maybe a callout to lynx can be made...
+ part.parts << TMail::Mail.parse("Content-Type: text/plain\r\n\r\n" + props.body) if props.body
+ # this may be automatically unwrapped from the rtf if the rtf includes the html
+ part.parts << TMail::Mail.parse("Content-Type: text/html\r\n\r\n" + props.body_html) if props.body_html
+ # temporarily disabled the rtf. its just showing up as an attachment anyway.
+ #mime.parts << Mime.new("Content-Type: text/rtf\r\n\r\n" + props.body_rtf) if props.body_rtf
+ # its thus currently possible to get no body at all if the only body is rtf. that is not
+ # really acceptable FIXME
+ part['Content-Type'] = 'multipart/alternative'
+ part
+ else
+ # check no header case. content type? etc?. not sure if my Mime class will accept
+ Log.debug "taking that other path"
+ # body can be nil, hence the to_s
+ TMail::Mail.parse "Content-Type: text/plain\r\n\r\n" + props.body.to_s
+ end
+ end
+
+ def to_tmail
+ # intended to be used for IPM.note, which is the email type. can use it for others if desired,
+ # YMMV
+ Log.warn "to_mime used on a #{props.message_class}" unless props.message_class == 'IPM.Note'
+ # we always have a body
+ mail = body = body_to_tmail
+
+ # If we have attachments, we take the current mime root (body), and make it the first child
+ # of a new tree that will contain body and attachments.
+ unless attachments.empty?
+ raise NotImplementedError
+ mime = Mime.new "Content-Type: multipart/mixed\r\n\r\n"
+ mime.parts << body
+ # i don't know any better way to do this. need multipart/related for inline images
+ # referenced by cid: urls to work, but don't want to use it otherwise...
+ related = false
+ attachments.each do |attach|
+ part = attach.to_mime
+ related = true if part.headers.has_key?('Content-ID') or part.headers.has_key?('Content-Location')
+ mime.parts << part
+ end
+ mime.headers['Content-Type'] = ['multipart/related'] if related
+ end
+
+ # at this point, mime is either
+ # - a single text/plain, consisting of the body ('taking that other path' above. rare)
+ # - a multipart/alternative, consiting of a few bodies (plain and html body. common)
+ # - a multipart/mixed, consisting of 1 of the above 2 types of bodies, and attachments.
+ # we add this standard preamble if its multipart
+ # FIXME preamble.replace, and body.replace both suck.
+ # preamble= is doable. body= wasn't being done because body will get rewritten from parts
+ # if multipart, and is only there readonly. can do that, or do a reparse...
+ # The way i do this means that only the first preamble will say it, not preambles of nested
+ # multipart chunks.
+ mail.quoted_body = "This is a multi-part message in MIME format.\r\n" if mail.multipart?
+
+ # now that we have a root, we can mix in all our headers
+ headers.each do |key, vals|
+ # don't overwrite the content-type, encoding style stuff
+ next if mail[key]
+ # some new temporary hacks
+ next if key =~ /content-type/i and vals[0] =~ /base64/
+ #next if mime.headers.keys.map(&:downcase).include? key.downcase
+ mail[key] = vals.first
+ end
+ # just a stupid hack to make the content-type header last, when using OrderedHash
+ #mime.headers['Content-Type'] = mime.headers.delete 'Content-Type'
+
+ mail
+ end
+ end
+
+ class Attachment
+ def to_tmail
+ # TODO: smarter mime typing.
+ mimetype = props.attach_mime_tag || 'application/octet-stream'
+ part = TMail::Mail.parse "Content-Type: #{mimetype}\r\n\r\n"
+ part['Content-Disposition'] = %{attachment; filename="#{filename}"}
+ part['Content-Transfer-Encoding'] = 'base64'
+ part['Content-Location'] = props.attach_content_location if props.attach_content_location
+ part['Content-ID'] = props.attach_content_id if props.attach_content_id
+ # data.to_s for now. data was nil for some reason.
+ # perhaps it was a data object not correctly handled?
+ # hmmm, have to use read here. that assumes that the data isa stream.
+ # but if the attachment data is a string, then it won't work. possible?
+ data_str = if @embedded_msg
+ raise NotImplementedError
+ mime.headers['Content-Type'] = 'message/rfc822'
+ # lets try making it not base64 for now
+ mime.headers.delete 'Content-Transfer-Encoding'
+ # not filename. rather name, or something else right?
+ # maybe it should be inline?? i forget attach_method / access meaning
+ mime.headers['Content-Disposition'] = [%{attachment; filename="#{@embedded_msg.subject}"}]
+ @embedded_msg.to_mime.to_s
+ elsif @embedded_ole
+ raise NotImplementedError
+ # kind of hacky
+ io = StringIO.new
+ Ole::Storage.new io do |ole|
+ ole.root.type = :dir
+ Ole::Storage::Dirent.copy @embedded_ole, ole.root
+ end
+ io.string
+ else
+ data.read.to_s
+ end
+ part.body = @embedded_msg ? data_str : Base64.encode64(data_str).gsub(/\n/, "\r\n")
+ part
+ end
+ end
+
+ class Msg < Message
+ def populate_headers
+ super
+ if !headers.has_key?('Date')
+ # can employ other methods for getting a time. heres one in a similar vein to msgconvert.pl,
+ # ie taking the time from an ole object
+ time = @root.ole.dirents.map { |dirent| dirent.modify_time || dirent.create_time }.compact.sort.last
+ headers['Date'] = [Time.iso8601(time.to_s).rfc2822] if time
+ end
+ end
+ end
+end
+
diff --git a/vendor/ruby-msg/lib/mapi/msg.rb b/vendor/ruby-msg/lib/mapi/msg.rb
new file mode 100644
index 000000000..fc30a9170
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi/msg.rb
@@ -0,0 +1,440 @@
+require 'rubygems'
+require 'ole/storage'
+require 'mapi'
+require 'mapi/rtf'
+
+module Mapi
+ #
+ # = Introduction
+ #
+ # Primary class interface to the vagaries of .msg files.
+ #
+ # The core of the work is done by the <tt>Msg::PropertyStore</tt> class.
+ #
+ class Msg < Message
+ #
+ # = Introduction
+ #
+ # A big compononent of +Msg+ files is the property store, which holds
+ # all the key/value pairs of properties. The message itself, and all
+ # its <tt>Attachment</tt>s and <tt>Recipient</tt>s have an instance of
+ # this class.
+ #
+ # = Storage model
+ #
+ # Property keys (tags?) can be either simple hex numbers, in the
+ # range 0x0000 - 0xffff, or they can be named properties. In fact,
+ # properties in the range 0x0000 to 0x7fff are supposed to be the non-
+ # named properties, and can be considered to be in the +PS_MAPI+
+ # namespace. (correct?)
+ #
+ # Named properties are serialized in the 0x8000 to 0xffff range,
+ # and are referenced as a guid and long/string pair.
+ #
+ # There are key ranges, which can be used to imply things generally
+ # about keys.
+ #
+ # Further, we can give symbolic names to most keys, coming from
+ # constants in various places. Eg:
+ #
+ # 0x0037 => subject
+ # {00062002-0000-0000-C000-000000000046}/0x8218 => response_status
+ # # displayed as categories in outlook
+ # {00020329-0000-0000-C000-000000000046}/"Keywords" => categories
+ #
+ # Futher, there are completely different names, coming from other
+ # object models that get mapped to these things (CDO's model,
+ # Outlook's model etc). Eg "urn:schemas:httpmail:subject"
+ # I think these can be ignored though, as they aren't defined clearly
+ # in terms of mapi properties, and i'm really just trying to make
+ # a mapi property store. (It should also be relatively easy to
+ # support them later.)
+ #
+ # = Usage
+ #
+ # The api is driven by a desire to have the simple stuff "just work", ie
+ #
+ # properties.subject
+ # properties.display_name
+ #
+ # There also needs to be a way to look up properties more specifically:
+ #
+ # properties[0x0037] # => gets the subject
+ # properties[0x0037, PS_MAPI] # => still gets the subject
+ # properties['Keywords', PS_PUBLIC_STRINGS] # => gets outlook's categories array
+ #
+ # The abbreviated versions work by "resolving" the symbols to full keys:
+ #
+ # # the guid here is just PS_PUBLIC_STRINGS
+ # properties.resolve :keywords # => #<Key {00020329-0000-0000-c000-000000000046}/"Keywords">
+ # # the result here is actually also a key
+ # k = properties.resolve :subject # => 0x0037
+ # # it has a guid
+ # k.guid == Msg::Properties::PS_MAPI # => true
+ #
+ # = Parsing
+ #
+ # There are three objects that need to be parsed to load a +Msg+ property store:
+ #
+ # 1. The +nameid+ directory (<tt>Properties.parse_nameid</tt>)
+ # 2. The many +substg+ objects, whose names should match <tt>Properties::SUBSTG_RX</tt>
+ # (<tt>Properties#parse_substg</tt>)
+ # 3. The +properties+ file (<tt>Properties#parse_properties</tt>)
+ #
+ # Understanding of the formats is by no means perfect.
+ #
+ # = TODO
+ #
+ # * While the key objects are sufficient, the value objects are just plain
+ # ruby types. It currently isn't possible to write to the values, or to know
+ # which encoding the value had.
+ # * Update this doc.
+ # * Perhaps change from eager loading, to be load-on-demand.
+ #
+ class PropertyStore
+ include PropertySet::Constants
+ Key = PropertySet::Key
+
+ # note that binary and default both use obj.open. not the block form. this means we should
+ # #close it later, which we don't. as we're only reading though, it shouldn't matter right?
+ # not really good though FIXME
+ # change these to use mapi symbolic const names
+ ENCODINGS = {
+ 0x000d => proc { |obj| obj }, # seems to be used when its going to be a directory instead of a file. eg nested ole. 3701 usually. in which case we shouldn't get here right?
+ 0x001f => proc { |obj| Ole::Types::FROM_UTF16.iconv obj.read }, # unicode
+ # ascii
+ # FIXME hack did a[0..-2] before, seems right sometimes, but for some others it chopped the text. chomp
+ 0x001e => proc { |obj| obj.read.chomp 0.chr },
+ 0x0102 => proc { |obj| obj.open }, # binary?
+ :default => proc { |obj| obj.open }
+ }
+
+ SUBSTG_RX = /^__substg1\.0_([0-9A-F]{4})([0-9A-F]{4})(?:-([0-9A-F]{8}))?$/
+ PROPERTIES_RX = /^__properties_version1\.0$/
+ NAMEID_RX = /^__nameid_version1\.0$/
+ VALID_RX = /#{SUBSTG_RX}|#{PROPERTIES_RX}|#{NAMEID_RX}/
+
+ attr_reader :nameid
+
+ def initialize
+ @nameid = nil
+ # not exactly a cache currently
+ @cache = {}
+ end
+
+ #--
+ # The parsing methods
+ #++
+
+ def self.load obj
+ prop = new
+ prop.load obj
+ prop
+ end
+
+ # Parse properties from the +Dirent+ obj
+ def load obj
+ # we need to do the nameid first, as it provides the map for later user defined properties
+ if nameid_obj = obj.children.find { |child| child.name =~ NAMEID_RX }
+ @nameid = PropertyStore.parse_nameid nameid_obj
+ # hack to make it available to all msg files from the same ole storage object
+ # FIXME - come up with a neater way
+ class << obj.ole
+ attr_accessor :msg_nameid
+ end
+ obj.ole.msg_nameid = @nameid
+ elsif obj.ole
+ @nameid = obj.ole.msg_nameid rescue nil
+ end
+ # now parse the actual properties. i think dirs that match the substg should be decoded
+ # as properties to. 0x000d is just another encoding, the dir encoding. it should match
+ # whether the object is file / dir. currently only example is embedded msgs anyway
+ obj.children.each do |child|
+ next unless child.file?
+ case child.name
+ when PROPERTIES_RX
+ parse_properties child
+ when SUBSTG_RX
+ parse_substg(*($~[1..-1].map { |num| num.hex rescue nil } + [child]))
+ end
+ end
+ end
+
+ # Read nameid from the +Dirent+ obj, which is used for mapping of named properties keys to
+ # proxy keys in the 0x8000 - 0xffff range.
+ # Returns a hash of integer -> Key.
+ def self.parse_nameid obj
+ remaining = obj.children.dup
+ guids_obj, props_obj, names_obj =
+ %w[__substg1.0_00020102 __substg1.0_00030102 __substg1.0_00040102].map do |name|
+ remaining.delete obj/name
+ end
+
+ # parse guids
+ # this is the guids for named properities (other than builtin ones)
+ # i think PS_PUBLIC_STRINGS, and PS_MAPI are builtin.
+ guids = [PS_PUBLIC_STRINGS] + guids_obj.read.scan(/.{16}/mn).map do |str|
+ Ole::Types.load_guid str
+ end
+
+ # parse names.
+ # the string ids for named properties
+ # they are no longer parsed, as they're referred to by offset not
+ # index. they are simply sequentially packed, as a long, giving
+ # the string length, then padding to 4 byte multiple, and repeat.
+ names_data = names_obj.read
+
+ # parse actual props.
+ # not sure about any of this stuff really.
+ # should flip a few bits in the real msg, to get a better understanding of how this works.
+ props = props_obj.read.scan(/.{8}/mn).map do |str|
+ flags, offset = str[4..-1].unpack 'v2'
+ # the property will be serialised as this pseudo property, mapping it to this named property
+ pseudo_prop = 0x8000 + offset
+ named = flags & 1 == 1
+ prop = if named
+ str_off = *str.unpack('V')
+ len = *names_data[str_off, 4].unpack('V')
+ Ole::Types::FROM_UTF16.iconv names_data[str_off + 4, len]
+ else
+ a, b = str.unpack('v2')
+ Log.debug "b not 0" if b != 0
+ a
+ end
+ # a bit sus
+ guid_off = flags >> 1
+ # missing a few builtin PS_*
+ Log.debug "guid off < 2 (#{guid_off})" if guid_off < 2
+ guid = guids[guid_off - 2]
+ [pseudo_prop, Key.new(prop, guid)]
+ end
+
+ #Log.warn "* ignoring #{remaining.length} objects in nameid" unless remaining.empty?
+ # this leaves a bunch of other unknown chunks of data with completely unknown meaning.
+ # pp [:unknown, child.name, child.data.unpack('H*')[0].scan(/.{16}/m)]
+ Hash[*props.flatten]
+ end
+
+ # Parse an +Dirent+, as per <tt>msgconvert.pl</tt>. This is how larger properties, such
+ # as strings, binary blobs, and other ole sub-directories (eg nested Msg) are stored.
+ def parse_substg key, encoding, offset, obj
+ if (encoding & 0x1000) != 0
+ if !offset
+ # there is typically one with no offset first, whose data is a series of numbers
+ # equal to the lengths of all the sub parts. gives an implied array size i suppose.
+ # maybe you can initialize the array at this time. the sizes are the same as all the
+ # ole object sizes anyway, its to pre-allocate i suppose.
+ #p obj.data.unpack('V*')
+ # ignore this one
+ return
+ else
+ # remove multivalue flag for individual pieces
+ encoding &= ~0x1000
+ end
+ else
+ Log.warn "offset specified for non-multivalue encoding #{obj.name}" if offset
+ offset = nil
+ end
+ # offset is for multivalue encodings.
+ unless encoder = ENCODINGS[encoding]
+ Log.warn "unknown encoding #{encoding}"
+ #encoder = proc { |obj| obj.io } #.read }. maybe not a good idea
+ encoder = ENCODINGS[:default]
+ end
+ add_property key, encoder[obj], offset
+ end
+
+ # For parsing the +properties+ file. Smaller properties are serialized in one chunk,
+ # such as longs, bools, times etc. The parsing has problems.
+ def parse_properties obj
+ data = obj.read
+ # don't really understand this that well...
+ pad = data.length % 16
+ unless (pad == 0 || pad == 8) and data[0...pad] == "\000" * pad
+ Log.warn "padding was not as expected #{pad} (#{data.length}) -> #{data[0...pad].inspect}"
+ end
+ data[pad..-1].scan(/.{16}/mn).each do |data|
+ property, encoding = ('%08x' % data.unpack('V')).scan /.{4}/
+ key = property.hex
+ # doesn't make any sense to me. probably because its a serialization of some internal
+ # outlook structure...
+ next if property == '0000'
+ case encoding
+ when '0102', '001e', '001f', '101e', '101f', '000d'
+ # ignore on purpose. not sure what its for
+ # multivalue versions ignored also
+ when '0003' # long
+ # don't know what all the other data is for
+ add_property key, *data[8, 4].unpack('V')
+ when '000b' # boolean
+ # again, heaps more data than needed. and its not always 0 or 1.
+ # they are in fact quite big numbers. this is wrong.
+# p [property, data[4..-1].unpack('H*')[0]]
+ add_property key, data[8, 4].unpack('V')[0] != 0
+ when '0040' # systime
+ # seems to work:
+ add_property key, Ole::Types.load_time(data[8..-1])
+ else
+ #Log.warn "ignoring data in __properties section, encoding: #{encoding}"
+ #Log << data.unpack('H*').inspect + "\n"
+ end
+ end
+ end
+
+ def add_property key, value, pos=nil
+ # map keys in the named property range through nameid
+ if Integer === key and key >= 0x8000
+ if !@nameid
+ Log.warn "no nameid section yet named properties used"
+ key = Key.new key
+ elsif real_key = @nameid[key]
+ key = real_key
+ else
+ # i think i hit these when i have a named property, in the PS_MAPI
+ # guid
+ Log.warn "property in named range not in nameid #{key.inspect}"
+ key = Key.new key
+ end
+ else
+ key = Key.new key
+ end
+ if pos
+ @cache[key] ||= []
+ Log.warn "duplicate property" unless Array === @cache[key]
+ # ^ this is actually a trickier problem. the issue is more that they must all be of
+ # the same type.
+ @cache[key][pos] = value
+ else
+ # take the last.
+ Log.warn "duplicate property #{key.inspect}" if @cache[key]
+ @cache[key] = value
+ end
+ end
+
+ # delegate to cache
+ def method_missing name, *args, &block
+ @cache.send name, *args, &block
+ end
+ end
+
+ # these 2 will actually be of the form
+ # 1\.0_#([0-9A-Z]{8}), where $1 is the 0 based index number in hex
+ # should i parse that and use it as an index, or just return in
+ # file order? probably should use it later...
+ ATTACH_RX = /^__attach_version1\.0_.*/
+ RECIP_RX = /^__recip_version1\.0_.*/
+ VALID_RX = /#{PropertyStore::VALID_RX}|#{ATTACH_RX}|#{RECIP_RX}/
+
+ attr_reader :root
+ attr_accessor :close_parent
+
+ # Alternate constructor, to create an +Msg+ directly from +arg+ and +mode+, passed
+ # directly to Ole::Storage (ie either filename or seekable IO object).
+ def self.open arg, mode=nil
+ msg = new Ole::Storage.open(arg, mode).root
+ # we will close the ole when we are #closed
+ msg.close_parent = true
+ if block_given?
+ begin yield msg
+ ensure; msg.close
+ end
+ else msg
+ end
+ end
+
+ # Create an Msg from +root+, an <tt>Ole::Storage::Dirent</tt> object
+ def initialize root
+ @root = root
+ @close_parent = false
+ super PropertySet.new(PropertyStore.load(@root))
+ Msg.warn_unknown @root
+ end
+
+ def self.warn_unknown obj
+ # bit of validation. not important if there is extra stuff, though would be
+ # interested to know what it is. doesn't check dir/file stuff.
+ unknown = obj.children.reject { |child| child.name =~ VALID_RX }
+ Log.warn "skipped #{unknown.length} unknown msg object(s)" unless unknown.empty?
+ end
+
+ def close
+ @root.ole.close if @close_parent
+ end
+
+ def attachments
+ @attachments ||= @root.children.
+ select { |child| child.dir? and child.name =~ ATTACH_RX }.
+ map { |child| Attachment.new child }.
+ select { |attach| attach.valid? }
+ end
+
+ def recipients
+ @recipients ||= @root.children.
+ select { |child| child.dir? and child.name =~ RECIP_RX }.
+ map { |child| Recipient.new child }
+ end
+
+ class Attachment < Mapi::Attachment
+ attr_reader :obj, :properties
+ alias props :properties
+
+ def initialize obj
+ @obj = obj
+ @embedded_ole = nil
+ @embedded_msg = nil
+
+ super PropertySet.new(PropertyStore.load(@obj))
+ Msg.warn_unknown @obj
+
+ @obj.children.each do |child|
+ # temp hack. PropertyStore doesn't do directory properties atm - FIXME
+ if child.dir? and child.name =~ PropertyStore::SUBSTG_RX and
+ $1 == '3701' and $2.downcase == '000d'
+ @embedded_ole = child
+ class << @embedded_ole
+ def compobj
+ return nil unless compobj = self["\001CompObj"]
+ compobj.read[/^.{32}([^\x00]+)/m, 1]
+ end
+
+ def embedded_type
+ temp = compobj and return temp
+ # try to guess more
+ if children.select { |child| child.name =~ /__(substg|properties|recip|attach|nameid)/ }.length > 2
+ return 'Microsoft Office Outlook Message'
+ end
+ nil
+ end
+ end
+ if @embedded_ole.embedded_type == 'Microsoft Office Outlook Message'
+ @embedded_msg = Msg.new @embedded_ole
+ end
+ end
+ end
+ end
+
+ def valid?
+ # something i started to notice when handling embedded ole object attachments is
+ # the particularly strange case where there are empty attachments
+ not props.raw.keys.empty?
+ end
+ end
+
+ #
+ # +Recipient+ serves as a container for the +recip+ directories in the .msg.
+ # It has things like office_location, business_telephone_number, but I don't
+ # think enough to make a vCard out of?
+ #
+ class Recipient < Mapi::Recipient
+ attr_reader :obj, :properties
+ alias props :properties
+
+ def initialize obj
+ @obj = obj
+ super PropertySet.new(PropertyStore.load(@obj))
+ Msg.warn_unknown @obj
+ end
+ end
+ end
+end
+
diff --git a/vendor/ruby-msg/lib/mapi/property_set.rb b/vendor/ruby-msg/lib/mapi/property_set.rb
new file mode 100644
index 000000000..199bca525
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi/property_set.rb
@@ -0,0 +1,269 @@
+require 'yaml'
+require 'mapi/types'
+require 'mapi/rtf'
+require 'rtf'
+
+module Mapi
+ #
+ # The Mapi::PropertySet class is used to wrap the lower level Msg or Pst property stores,
+ # and provide a consistent and more friendly interface. It allows you to just say:
+ #
+ # properties.subject
+ #
+ # instead of:
+ #
+ # properites.raw[0x0037, PS_MAPI]
+ #
+ # The underlying store can be just a hash, or lazily loading directly from the file. A good
+ # compromise is to cache all the available keys, and just return the values on demand, rather
+ # than load up many possibly unwanted values.
+ #
+ class PropertySet
+ # the property set guid constants
+ # these guids are all defined with the macro DEFINE_OLEGUID in mapiguid.h.
+ # see http://doc.ddart.net/msdn/header/include/mapiguid.h.html
+ oleguid = proc do |prefix|
+ Ole::Types::Clsid.parse "{#{prefix}-0000-0000-c000-000000000046}"
+ end
+
+ NAMES = {
+ oleguid['00020328'] => 'PS_MAPI',
+ oleguid['00020329'] => 'PS_PUBLIC_STRINGS',
+ oleguid['00020380'] => 'PS_ROUTING_EMAIL_ADDRESSES',
+ oleguid['00020381'] => 'PS_ROUTING_ADDRTYPE',
+ oleguid['00020382'] => 'PS_ROUTING_DISPLAY_NAME',
+ oleguid['00020383'] => 'PS_ROUTING_ENTRYID',
+ oleguid['00020384'] => 'PS_ROUTING_SEARCH_KEY',
+ # string properties in this namespace automatically get added to the internet headers
+ oleguid['00020386'] => 'PS_INTERNET_HEADERS',
+ # theres are bunch of outlook ones i think
+ # http://blogs.msdn.com/stephen_griffin/archive/2006/05/10/outlook-2007-beta-documentation-notification-based-indexing-support.aspx
+ # IPM.Appointment
+ oleguid['00062002'] => 'PSETID_Appointment',
+ # IPM.Task
+ oleguid['00062003'] => 'PSETID_Task',
+ # used for IPM.Contact
+ oleguid['00062004'] => 'PSETID_Address',
+ oleguid['00062008'] => 'PSETID_Common',
+ # didn't find a source for this name. it is for IPM.StickyNote
+ oleguid['0006200e'] => 'PSETID_Note',
+ # for IPM.Activity. also called the journal?
+ oleguid['0006200a'] => 'PSETID_Log',
+ }
+
+ module Constants
+ NAMES.each { |guid, name| const_set name, guid }
+ end
+
+ include Constants
+
+ # +Properties+ are accessed by <tt>Key</tt>s, which are coerced to this class.
+ # Includes a bunch of methods (hash, ==, eql?) to allow it to work as a key in
+ # a +Hash+.
+ #
+ # Also contains the code that maps keys to symbolic names.
+ class Key
+ include Constants
+
+ attr_reader :code, :guid
+ def initialize code, guid=PS_MAPI
+ @code, @guid = code, guid
+ end
+
+ def to_sym
+ # hmmm, for some stuff, like, eg, the message class specific range, sym-ification
+ # of the key depends on knowing our message class. i don't want to store anything else
+ # here though, so if that kind of thing is needed, it can be passed to this function.
+ # worry about that when some examples arise.
+ case code
+ when Integer
+ if guid == PS_MAPI # and < 0x8000 ?
+ # the hash should be updated now that i've changed the process
+ TAGS['%04x' % code].first[/_(.*)/, 1].downcase.to_sym rescue code
+ else
+ # handle other guids here, like mapping names to outlook properties, based on the
+ # outlook object model.
+ NAMED_MAP[self].to_sym rescue code
+ end
+ when String
+ # return something like
+ # note that named properties don't go through the map at the moment. so #categories
+ # doesn't work yet
+ code.downcase.to_sym
+ end
+ end
+
+ def to_s
+ to_sym.to_s
+ end
+
+ # FIXME implement these
+ def transmittable?
+ # etc, can go here too
+ end
+
+ # this stuff is to allow it to be a useful key
+ def hash
+ [code, guid].hash
+ end
+
+ def == other
+ hash == other.hash
+ end
+
+ alias eql? :==
+
+ def inspect
+ # maybe the way to do this, would be to be able to register guids
+ # in a global lookup, which are used by Clsid#inspect itself, to
+ # provide symbolic names...
+ guid_str = NAMES[guid] || "{#{guid.format}}" rescue "nil"
+ if Integer === code
+ hex = '0x%04x' % code
+ if guid == PS_MAPI
+ # just display as plain hex number
+ hex
+ else
+ "#<Key #{guid_str}/#{hex}>"
+ end
+ else
+ # display full guid and code
+ "#<Key #{guid_str}/#{code.inspect}>"
+ end
+ end
+ end
+
+ # duplicated here for now
+ SUPPORT_DIR = File.dirname(__FILE__) + '/../..'
+
+ # data files that provide for the code to symbolic name mapping
+ # guids in named_map are really constant references to the above
+ TAGS = YAML.load_file "#{SUPPORT_DIR}/data/mapitags.yaml"
+ NAMED_MAP = YAML.load_file("#{SUPPORT_DIR}/data/named_map.yaml").inject({}) do |hash, (key, value)|
+ hash.update Key.new(key[0], const_get(key[1])) => value
+ end
+
+ attr_reader :raw
+
+ # +raw+ should be an hash-like object that maps <tt>Key</tt>s to values. Should respond_to?
+ # [], keys, values, each, and optionally []=, and delete.
+ def initialize raw
+ @raw = raw
+ end
+
+ # resolve +arg+ (could be key, code, string, or symbol), and possible +guid+ to a key.
+ # returns nil on failure
+ def resolve arg, guid=nil
+ if guid; Key.new arg, guid
+ else
+ case arg
+ when Key; arg
+ when Integer; Key.new arg
+ else sym_to_key[arg.to_sym]
+ end
+ end
+ end
+
+ # this is the function that creates a symbol to key mapping. currently this works by making a
+ # pass through the raw properties, but conceivably you could map symbols to keys using the
+ # mapitags directly. problem with that would be that named properties wouldn't map automatically,
+ # but maybe thats not too important.
+ def sym_to_key
+ return @sym_to_key if @sym_to_key
+ @sym_to_key = {}
+ raw.keys.each do |key|
+ sym = key.to_sym
+ unless Symbol === sym
+ Log.debug "couldn't find symbolic name for key #{key.inspect}"
+ next
+ end
+ if @sym_to_key[sym]
+ Log.warn "duplicate key #{key.inspect}"
+ # we give preference to PS_MAPI keys
+ @sym_to_key[sym] = key if key.guid == PS_MAPI
+ else
+ # just assign
+ @sym_to_key[sym] = key
+ end
+ end
+ @sym_to_key
+ end
+
+ def keys
+ sym_to_key.keys
+ end
+
+ def values
+ sym_to_key.values.map { |key| raw[key] }
+ end
+
+ def [] arg, guid=nil
+ raw[resolve(arg, guid)]
+ end
+
+ def []= arg, *args
+ args.unshift nil if args.length == 1
+ guid, value = args
+ # FIXME this won't really work properly. it would need to go
+ # to TAGS to resolve, as it often won't be there already...
+ raw[resolve(arg, guid)] = value
+ end
+
+ def method_missing name, *args
+ if name.to_s !~ /\=$/ and args.empty?
+ self[name]
+ elsif name.to_s =~ /(.*)\=$/ and args.length == 1
+ self[$1] = args[0]
+ else
+ super
+ end
+ end
+
+ def to_h
+ sym_to_key.inject({}) { |hash, (sym, key)| hash.update sym => raw[key] }
+ end
+
+ def inspect
+ "#<#{self.class} " + to_h.sort_by { |k, v| k.to_s }.map do |k, v|
+ v = v.inspect
+ "#{k}=#{v.length > 32 ? v[0..29] + '..."' : v}"
+ end.join(' ') + '>'
+ end
+
+ # -----
+
+ # temporary pseudo tags
+
+ # for providing rtf to plain text conversion. later, html to text too.
+ def body
+ return @body if defined?(@body)
+ @body = (self[:body] rescue nil)
+ # last resort
+ if !@body or @body.strip.empty?
+ Log.warn 'creating text body from rtf'
+ @body = (::RTF::Converter.rtf2text body_rtf rescue nil)
+ end
+ @body
+ end
+
+ # for providing rtf decompression
+ def body_rtf
+ return @body_rtf if defined?(@body_rtf)
+ @body_rtf = (RTF.rtfdecompr rtf_compressed.read rescue nil)
+ end
+
+ # for providing rtf to html conversion
+ def body_html
+ return @body_html if defined?(@body_html)
+ @body_html = (self[:body_html].read rescue nil)
+ @body_html = (RTF.rtf2html body_rtf rescue nil) if !@body_html or @body_html.strip.empty?
+ # last resort
+ if !@body_html or @body_html.strip.empty?
+ Log.warn 'creating html body from rtf'
+ @body_html = (::RTF::Converter.rtf2text body_rtf, :html rescue nil)
+ end
+ @body_html
+ end
+ end
+end
+
diff --git a/vendor/ruby-msg/lib/mapi/pst.rb b/vendor/ruby-msg/lib/mapi/pst.rb
new file mode 100644
index 000000000..9ac64b097
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi/pst.rb
@@ -0,0 +1,1806 @@
+#
+# = Introduction
+#
+# This file is mostly an attempt to port libpst to ruby, and simplify it in the process. It
+# will leverage much of the existing MAPI => MIME conversion developed for Msg files, and as
+# such is purely concerned with the file structure details.
+#
+# = TODO
+#
+# 1. solve recipient table problem (test4).
+# this is done. turns out it was due to id2 clashes. find better solution
+# 2. check parse consistency. an initial conversion of a 30M file to pst, shows
+# a number of messages conveting badly. compare with libpst too.
+# 3. xattribs
+# 4. generalise the Mapi stuff better
+# 5. refactor index load
+# 6. msg serialization?
+#
+
+=begin
+
+quick plan for cleanup.
+
+have working tests for 97 and 03 file formats, so safe.
+
+want to fix up:
+
+64 bit unpacks scattered around. its ugly. not sure how best to handle it, but am slightly tempted
+to override String#unpack to support a 64 bit little endian unpack (like L vs N/V, for Q). one way or
+another need to fix it. Could really slow everything else down if its parsing the unpack strings twice,
+once in ruby, for every single unpack i do :/
+
+the index loading process, and the lack of shared code between normal vs 64 bit variants, and Index vs Desc.
+should be able to reduce code by factor of 4. also think I should move load code into the class too. then
+maybe have something like:
+
+class Header
+ def index_class
+ version_2003 ? Index64 : Index
+ end
+end
+
+def load_idx
+ header.index_class.load_index
+end
+
+OR
+
+def initialize
+ @header = ...
+ extend @header.index_class::Load
+ load_idx
+end
+
+need to think about the role of the mapi code, and Pst::Item etc, but that layer can come later.
+
+=end
+
+require 'mapi'
+require 'enumerator'
+require 'ostruct'
+require 'ole/ranges_io'
+
+module Mapi
+class Pst
+ class FormatError < StandardError
+ end
+
+ # unfortunately there is no Q analogue which is little endian only.
+ # this translates T as an unsigned quad word, little endian byte order, to
+ # not pollute the rest of the code.
+ #
+ # didn't want to override String#unpack, cause its too hacky, and incomplete.
+ def self.unpack str, unpack_spec
+ return str.unpack(unpack_spec) unless unpack_spec['T']
+ @unpack_cache ||= {}
+ t_offsets, new_spec = @unpack_cache[unpack_spec]
+ unless t_offsets
+ t_offsets = []
+ offset = 0
+ new_spec = ''
+ unpack_spec.scan(/([^\d])_?(\*|\d+)?/o) do
+ num_elems = $1.downcase == 'a' ? 1 : ($2 || 1).to_i
+ if $1 == 'T'
+ num_elems.times { |i| t_offsets << offset + i }
+ new_spec << "V#{num_elems * 2}"
+ else
+ new_spec << $~[0]
+ end
+ offset += num_elems
+ end
+ @unpack_cache[unpack_spec] = [t_offsets, new_spec]
+ end
+ a = str.unpack(new_spec)
+ t_offsets.each do |offset|
+ low, high = a[offset, 2]
+ a[offset, 2] = low && high ? low + (high << 32) : nil
+ end
+ a
+ end
+
+ #
+ # this is the header and encryption encapsulation code
+ # ----------------------------------------------------------------------------
+ #
+
+ # class which encapsulates the pst header
+ class Header
+ SIZE = 512
+ MAGIC = 0x2142444e
+
+ # these are the constants defined in libpst.c, that
+ # are referenced in pst_open()
+ INDEX_TYPE_OFFSET = 0x0A
+ FILE_SIZE_POINTER = 0xA8
+ FILE_SIZE_POINTER_64 = 0xB8
+ SECOND_POINTER = 0xBC
+ INDEX_POINTER = 0xC4
+ SECOND_POINTER_64 = 0xE0
+ INDEX_POINTER_64 = 0xF0
+ ENC_OFFSET = 0x1CD
+
+ attr_reader :magic, :index_type, :encrypt_type, :size
+ attr_reader :index1_count, :index1, :index2_count, :index2
+ attr_reader :version
+ def initialize data
+ @magic = data.unpack('N')[0]
+ @index_type = data[INDEX_TYPE_OFFSET]
+ @version = {0x0e => 1997, 0x17 => 2003}[@index_type]
+
+ if version_2003?
+ # don't know?
+ # >> data1.unpack('V*').zip(data2.unpack('V*')).enum_with_index.select { |(c, d), i| c != d and not [46, 56, 60].include?(i) }.select { |(a, b), i| b == 0 }.map { |(a, b), i| [a / 256, i] }
+ # [8, 76], [32768, 84], [128, 89]
+ # >> data1.unpack('C*').zip(data2.unpack('C*')).enum_with_index.select { |(c, d), i| c != d and not [184..187, 224..227, 240..243].any? { |r| r === i } }.select { |(a, b), i| b == 0 and ((Math.log(a) / Math.log(2)) % 1) < 0.0001 }
+ # [[[2, 0], 61], [[2, 0], 76], [[2, 0], 195], [[2, 0], 257], [[8, 0], 305], [[128, 0], 338], [[128, 0], 357]]
+ # i have only 2 psts to base this guess on, so i can't really come up with anything that looks reasonable yet. not sure what the offset is. unfortunately there is so much in the header
+ # that isn't understood...
+ @encrypt_type = 1
+
+ @index2_count, @index2 = data[SECOND_POINTER_64 - 4, 8].unpack('V2')
+ @index1_count, @index1 = data[INDEX_POINTER_64 - 4, 8].unpack('V2')
+
+ @size = data[FILE_SIZE_POINTER_64, 4].unpack('V')[0]
+ else
+ @encrypt_type = data[ENC_OFFSET]
+
+ @index2_count, @index2 = data[SECOND_POINTER - 4, 8].unpack('V2')
+ @index1_count, @index1 = data[INDEX_POINTER - 4, 8].unpack('V2')
+
+ @size = data[FILE_SIZE_POINTER, 4].unpack('V')[0]
+ end
+
+ validate!
+ end
+
+ def version_2003?
+ version == 2003
+ end
+
+ def encrypted?
+ encrypt_type != 0
+ end
+
+ def validate!
+ raise FormatError, "bad signature on pst file (#{'0x%x' % magic})" unless magic == MAGIC
+ raise FormatError, "only index types 0x0e and 0x17 are handled (#{'0x%x' % index_type})" unless [0x0e, 0x17].include?(index_type)
+ raise FormatError, "only encrytion types 0 and 1 are handled (#{encrypt_type.inspect})" unless [0, 1].include?(encrypt_type)
+ end
+ end
+
+ # compressible encryption! :D
+ #
+ # simple substitution. see libpst.c
+ # maybe test switch to using a String#tr!
+ class CompressibleEncryption
+ DECRYPT_TABLE = [
+ 0x47, 0xf1, 0xb4, 0xe6, 0x0b, 0x6a, 0x72, 0x48,
+ 0x85, 0x4e, 0x9e, 0xeb, 0xe2, 0xf8, 0x94, 0x53, # 0x0f
+ 0xe0, 0xbb, 0xa0, 0x02, 0xe8, 0x5a, 0x09, 0xab,
+ 0xdb, 0xe3, 0xba, 0xc6, 0x7c, 0xc3, 0x10, 0xdd, # 0x1f
+ 0x39, 0x05, 0x96, 0x30, 0xf5, 0x37, 0x60, 0x82,
+ 0x8c, 0xc9, 0x13, 0x4a, 0x6b, 0x1d, 0xf3, 0xfb, # 0x2f
+ 0x8f, 0x26, 0x97, 0xca, 0x91, 0x17, 0x01, 0xc4,
+ 0x32, 0x2d, 0x6e, 0x31, 0x95, 0xff, 0xd9, 0x23, # 0x3f
+ 0xd1, 0x00, 0x5e, 0x79, 0xdc, 0x44, 0x3b, 0x1a,
+ 0x28, 0xc5, 0x61, 0x57, 0x20, 0x90, 0x3d, 0x83, # 0x4f
+ 0xb9, 0x43, 0xbe, 0x67, 0xd2, 0x46, 0x42, 0x76,
+ 0xc0, 0x6d, 0x5b, 0x7e, 0xb2, 0x0f, 0x16, 0x29, # 0x5f
+ 0x3c, 0xa9, 0x03, 0x54, 0x0d, 0xda, 0x5d, 0xdf,
+ 0xf6, 0xb7, 0xc7, 0x62, 0xcd, 0x8d, 0x06, 0xd3, # 0x6f
+ 0x69, 0x5c, 0x86, 0xd6, 0x14, 0xf7, 0xa5, 0x66,
+ 0x75, 0xac, 0xb1, 0xe9, 0x45, 0x21, 0x70, 0x0c, # 0x7f
+ 0x87, 0x9f, 0x74, 0xa4, 0x22, 0x4c, 0x6f, 0xbf,
+ 0x1f, 0x56, 0xaa, 0x2e, 0xb3, 0x78, 0x33, 0x50, # 0x8f
+ 0xb0, 0xa3, 0x92, 0xbc, 0xcf, 0x19, 0x1c, 0xa7,
+ 0x63, 0xcb, 0x1e, 0x4d, 0x3e, 0x4b, 0x1b, 0x9b, # 0x9f
+ 0x4f, 0xe7, 0xf0, 0xee, 0xad, 0x3a, 0xb5, 0x59,
+ 0x04, 0xea, 0x40, 0x55, 0x25, 0x51, 0xe5, 0x7a, # 0xaf
+ 0x89, 0x38, 0x68, 0x52, 0x7b, 0xfc, 0x27, 0xae,
+ 0xd7, 0xbd, 0xfa, 0x07, 0xf4, 0xcc, 0x8e, 0x5f, # 0xbf
+ 0xef, 0x35, 0x9c, 0x84, 0x2b, 0x15, 0xd5, 0x77,
+ 0x34, 0x49, 0xb6, 0x12, 0x0a, 0x7f, 0x71, 0x88, # 0xcf
+ 0xfd, 0x9d, 0x18, 0x41, 0x7d, 0x93, 0xd8, 0x58,
+ 0x2c, 0xce, 0xfe, 0x24, 0xaf, 0xde, 0xb8, 0x36, # 0xdf
+ 0xc8, 0xa1, 0x80, 0xa6, 0x99, 0x98, 0xa8, 0x2f,
+ 0x0e, 0x81, 0x65, 0x73, 0xe4, 0xc2, 0xa2, 0x8a, # 0xef
+ 0xd4, 0xe1, 0x11, 0xd0, 0x08, 0x8b, 0x2a, 0xf2,
+ 0xed, 0x9a, 0x64, 0x3f, 0xc1, 0x6c, 0xf9, 0xec # 0xff
+ ]
+
+ ENCRYPT_TABLE = [nil] * 256
+ DECRYPT_TABLE.each_with_index { |i, j| ENCRYPT_TABLE[i] = j }
+
+ def self.decrypt_alt encrypted
+ decrypted = ''
+ encrypted.length.times { |i| decrypted << DECRYPT_TABLE[encrypted[i]] }
+ decrypted
+ end
+
+ def self.encrypt_alt decrypted
+ encrypted = ''
+ decrypted.length.times { |i| encrypted << ENCRYPT_TABLE[decrypted[i]] }
+ encrypted
+ end
+
+ # an alternate implementation that is possibly faster....
+ # TODO - bench
+ DECRYPT_STR, ENCRYPT_STR = [DECRYPT_TABLE, (0...256)].map do |values|
+ values.map { |i| i.chr }.join.gsub(/([\^\-\\])/, "\\\\\\1")
+ end
+
+ def self.decrypt encrypted
+ encrypted.tr ENCRYPT_STR, DECRYPT_STR
+ end
+
+ def self.encrypt decrypted
+ decrypted.tr DECRYPT_STR, ENCRYPT_STR
+ end
+ end
+
+ class RangesIOEncryptable < RangesIO
+ def initialize io, mode='r', params={}
+ mode, params = 'r', mode if Hash === mode
+ @decrypt = !!params[:decrypt]
+ super
+ end
+
+ def encrypted?
+ @decrypt
+ end
+
+ def read limit=nil
+ buf = super
+ buf = CompressibleEncryption.decrypt(buf) if encrypted?
+ buf
+ end
+ end
+
+ attr_reader :io, :header, :idx, :desc, :special_folder_ids
+
+ # corresponds to
+ # * pst_open
+ # * pst_load_index
+ def initialize io
+ @io = io
+ io.pos = 0
+ @header = Header.new io.read(Header::SIZE)
+
+ # would prefer this to be in Header#validate, but it doesn't have the io size.
+ # should perhaps downgrade this to just be a warning...
+ raise FormatError, "header size field invalid (#{header.size} != #{io.size}}" unless header.size == io.size
+
+ load_idx
+ load_desc
+ load_xattrib
+
+ @special_folder_ids = {}
+ end
+
+ def encrypted?
+ @header.encrypted?
+ end
+
+ # until i properly fix logging...
+ def warn s
+ Mapi::Log.warn s
+ end
+
+ #
+ # this is the index and desc record loading code
+ # ----------------------------------------------------------------------------
+ #
+
+ ToTree = Module.new
+
+ module Index2
+ BLOCK_SIZE = 512
+ module RecursiveLoad
+ def load_chain
+ #...
+ end
+ end
+
+ module Base
+ def read
+ #...
+ end
+ end
+
+ class Version1997 < Struct.new(:a)#...)
+ SIZE = 12
+
+ include RecursiveLoad
+ include Base
+ end
+
+ class Version2003 < Struct.new(:a)#...)
+ SIZE = 24
+
+ include RecursiveLoad
+ include Base
+ end
+ end
+
+ module Desc2
+ module Base
+ def desc
+ #...
+ end
+ end
+
+ class Version1997 < Struct.new(:a)#...)
+ #include Index::RecursiveLoad
+ include Base
+ end
+
+ class Version2003 < Struct.new(:a)#...)
+ #include Index::RecursiveLoad
+ include Base
+ end
+ end
+
+ # more constants from libpst.c
+ # these relate to the index block
+ ITEM_COUNT_OFFSET = 0x1f0 # count byte
+ LEVEL_INDICATOR_OFFSET = 0x1f3 # node or leaf
+ BACKLINK_OFFSET = 0x1f8 # backlink u1 value
+
+ # these 3 classes are used to hold various file records
+
+ # pst_index
+ class Index < Struct.new(:id, :offset, :size, :u1)
+ UNPACK_STR = 'VVvv'
+ SIZE = 12
+ BLOCK_SIZE = 512 # index blocks was 516 but bogus
+ COUNT_MAX = 41 # max active items (ITEM_COUNT_OFFSET / Index::SIZE = 41)
+
+ attr_accessor :pst
+ def initialize data
+ data = Pst.unpack data, UNPACK_STR if String === data
+ super(*data)
+ end
+
+ def type
+ @type ||= begin
+ if id & 0x2 == 0
+ :data
+ else
+ first_byte, second_byte = read.unpack('CC')
+ if first_byte == 1
+ raise second_byte unless second_byte == 1
+ :data_chain_header
+ elsif first_byte == 2
+ raise second_byte unless second_byte == 0
+ :id2_assoc
+ else
+ raise FormatError, 'unknown first byte for block - %p' % first_byte
+ end
+ end
+ end
+ end
+
+ def data?
+ (id & 0x2) == 0
+ end
+
+ def read decrypt=true
+ # only data blocks are every encrypted
+ decrypt = false unless data?
+ pst.pst_read_block_size offset, size, decrypt
+ end
+
+ # show all numbers in hex
+ def inspect
+ super.gsub(/=(\d+)/) { '=0x%x' % $1.to_i }.sub(/Index /, "Index type=#{type.inspect}, ")
+ end
+ end
+
+ # mostly guesses.
+ ITEM_COUNT_OFFSET_64 = 0x1e8
+ LEVEL_INDICATOR_OFFSET_64 = 0x1eb # diff of 3 between these 2 as above...
+
+ # will maybe inherit from Index64, in order to get the same #type function.
+ class Index64 < Index
+ UNPACK_STR = 'TTvvV'
+ SIZE = 24
+ BLOCK_SIZE = 512
+ COUNT_MAX = 20 # bit of a guess really. 512 / 24 = 21, but doesn't leave enough header room
+
+ # this is the extra item on the end of the UNPACK_STR above
+ attr_accessor :u2
+
+ def initialize data
+ data = Pst.unpack data, UNPACK_STR if String === data
+ @u2 = data.pop
+ super data
+ end
+
+ def inspect
+ super.sub(/>$/, ', u2=%p>' % u2)
+ end
+
+ def self.load_chain io, header
+ load_idx_rec io, header.index1, 0, 0
+ end
+
+ # almost identical to load code for Index, just different offsets and unpack strings.
+ # can probably merge them, or write a generic load_tree function or something.
+ def self.load_idx_rec io, offset, linku1, start_val
+ io.seek offset
+ buf = io.read BLOCK_SIZE
+ idxs = []
+
+ item_count = buf[ITEM_COUNT_OFFSET_64]
+ raise "have too many active items in index (#{item_count})" if item_count > COUNT_MAX
+
+ #idx = Index.new buf[BACKLINK_OFFSET, Index::SIZE]
+ #raise 'blah 1' unless idx.id == linku1
+
+ if buf[LEVEL_INDICATOR_OFFSET_64] == 0
+ # leaf pointers
+ # split the data into item_count index objects
+ buf[0, SIZE * item_count].scan(/.{#{SIZE}}/mo).each_with_index do |data, i|
+ idx = new data
+ # first entry
+ raise 'blah 3' if i == 0 and start_val != 0 and idx.id != start_val
+ #idx.pst = self
+ break if idx.id == 0
+ idxs << idx
+ end
+ else
+ # node pointers
+ # split the data into item_count table pointers
+ buf[0, SIZE * item_count].scan(/.{#{SIZE}}/mo).each_with_index do |data, i|
+ start, u1, offset = Pst.unpack data, 'T3'
+ # for the first value, we expect the start to be equal
+ raise 'blah 3' if i == 0 and start_val != 0 and start != start_val
+ break if start == 0
+ idxs += load_idx_rec io, offset, u1, start
+ end
+ end
+
+ idxs
+ end
+ end
+
+ # pst_desc
+ class Desc64 < Struct.new(:desc_id, :idx_id, :idx2_id, :parent_desc_id, :u2)
+ UNPACK_STR = 'T3VV'
+ SIZE = 32
+ BLOCK_SIZE = 512 # descriptor blocks was 520 but bogus
+ COUNT_MAX = 15 # guess as per Index64
+
+ include RecursivelyEnumerable
+
+ attr_accessor :pst
+ attr_reader :children
+ def initialize data
+ super(*Pst.unpack(data, UNPACK_STR))
+ @children = []
+ end
+
+ def desc
+ pst.idx_from_id idx_id
+ end
+
+ def list_index
+ pst.idx_from_id idx2_id
+ end
+
+ def self.load_chain io, header
+ load_desc_rec io, header.index2, 0, 0x21
+ end
+
+ def self.load_desc_rec io, offset, linku1, start_val
+ io.seek offset
+ buf = io.read BLOCK_SIZE
+ descs = []
+ item_count = buf[ITEM_COUNT_OFFSET_64]
+
+ # not real desc
+ #desc = Desc.new buf[BACKLINK_OFFSET, 4]
+ #raise 'blah 1' unless desc.desc_id == linku1
+
+ if buf[LEVEL_INDICATOR_OFFSET_64] == 0
+ # leaf pointers
+ raise "have too many active items in index (#{item_count})" if item_count > COUNT_MAX
+ # split the data into item_count desc objects
+ buf[0, SIZE * item_count].scan(/.{#{SIZE}}/mo).each_with_index do |data, i|
+ desc = new data
+ # first entry
+ raise 'blah 3' if i == 0 and start_val != 0 and desc.desc_id != start_val
+ break if desc.desc_id == 0
+ descs << desc
+ end
+ else
+ # node pointers
+ raise "have too many active items in index (#{item_count})" if item_count > Index64::COUNT_MAX
+ # split the data into item_count table pointers
+ buf[0, Index64::SIZE * item_count].scan(/.{#{Index64::SIZE}}/mo).each_with_index do |data, i|
+ start, u1, offset = Pst.unpack data, 'T3'
+ # for the first value, we expect the start to be equal note that ids -1, so even for the
+ # first we expect it to be equal. thats the 0x21 (dec 33) desc record. this means we assert
+ # that the first desc record is always 33...
+ # thats because 0x21 is the pst root itself...
+ raise 'blah 3' if i == 0 and start_val != -1 and start != start_val
+ # this shouldn't really happen i'd imagine
+ break if start == 0
+ descs += load_desc_rec io, offset, u1, start
+ end
+ end
+
+ descs
+ end
+
+ def each_child(&block)
+ @children.each(&block)
+ end
+ end
+
+ # _pst_table_ptr_struct
+ class TablePtr < Struct.new(:start, :u1, :offset)
+ UNPACK_STR = 'V3'
+ SIZE = 12
+
+ def initialize data
+ data = data.unpack(UNPACK_STR) if String === data
+ super(*data)
+ end
+ end
+
+ # pst_desc
+ # idx_id is a pointer to an idx record which gets the primary data stream for the Desc record.
+ # idx2_id gets you an idx record, that when read gives you an ID2 association list, which just maps
+ # another set of ids to index values
+ class Desc < Struct.new(:desc_id, :idx_id, :idx2_id, :parent_desc_id)
+ UNPACK_STR = 'V4'
+ SIZE = 16
+ BLOCK_SIZE = 512 # descriptor blocks was 520 but bogus
+ COUNT_MAX = 31 # max active desc records (ITEM_COUNT_OFFSET / Desc::SIZE = 31)
+
+ include ToTree
+
+ attr_accessor :pst
+ attr_reader :children
+ def initialize data
+ super(*data.unpack(UNPACK_STR))
+ @children = []
+ end
+
+ def desc
+ pst.idx_from_id idx_id
+ end
+
+ def list_index
+ pst.idx_from_id idx2_id
+ end
+
+ # show all numbers in hex
+ def inspect
+ super.gsub(/=(\d+)/) { '=0x%x' % $1.to_i }
+ end
+ end
+
+ # corresponds to
+ # * _pst_build_id_ptr
+ def load_idx
+ @idx = []
+ @idx_offsets = []
+ if header.version_2003?
+ @idx = Index64.load_chain io, header
+ @idx.each { |idx| idx.pst = self }
+ else
+ load_idx_rec header.index1, header.index1_count, 0
+ end
+
+ # we'll typically be accessing by id, so create a hash as a lookup cache
+ @idx_from_id = {}
+ @idx.each do |idx|
+ warn "there are duplicate idx records with id #{idx.id}" if @idx_from_id[idx.id]
+ @idx_from_id[idx.id] = idx
+ end
+ end
+
+ # load the flat idx table, which maps ids to file ranges. this is the recursive helper
+ #
+ # corresponds to
+ # * _pst_build_id_ptr
+ def load_idx_rec offset, linku1, start_val
+ @idx_offsets << offset
+
+ #_pst_read_block_size(pf, offset, BLOCK_SIZE, &buf, 0, 0) < BLOCK_SIZE)
+ buf = pst_read_block_size offset, Index::BLOCK_SIZE, false
+
+ item_count = buf[ITEM_COUNT_OFFSET]
+ raise "have too many active items in index (#{item_count})" if item_count > Index::COUNT_MAX
+
+ idx = Index.new buf[BACKLINK_OFFSET, Index::SIZE]
+ raise 'blah 1' unless idx.id == linku1
+
+ if buf[LEVEL_INDICATOR_OFFSET] == 0
+ # leaf pointers
+ # split the data into item_count index objects
+ buf[0, Index::SIZE * item_count].scan(/.{#{Index::SIZE}}/mo).each_with_index do |data, i|
+ idx = Index.new data
+ # first entry
+ raise 'blah 3' if i == 0 and start_val != 0 and idx.id != start_val
+ idx.pst = self
+ # this shouldn't really happen i'd imagine
+ break if idx.id == 0
+ @idx << idx
+ end
+ else
+ # node pointers
+ # split the data into item_count table pointers
+ buf[0, TablePtr::SIZE * item_count].scan(/.{#{TablePtr::SIZE}}/mo).each_with_index do |data, i|
+ table = TablePtr.new data
+ # for the first value, we expect the start to be equal
+ raise 'blah 3' if i == 0 and start_val != 0 and table.start != start_val
+ # this shouldn't really happen i'd imagine
+ break if table.start == 0
+ load_idx_rec table.offset, table.u1, table.start
+ end
+ end
+ end
+
+ # most access to idx objects will use this function
+ #
+ # corresponds to
+ # * _pst_getID
+ def idx_from_id id
+ @idx_from_id[id]
+ end
+
+ # corresponds to
+ # * _pst_build_desc_ptr
+ # * record_descriptor
+ def load_desc
+ @desc = []
+ @desc_offsets = []
+ if header.version_2003?
+ @desc = Desc64.load_chain io, header
+ @desc.each { |desc| desc.pst = self }
+ else
+ load_desc_rec header.index2, header.index2_count, 0x21
+ end
+
+ # first create a lookup cache
+ @desc_from_id = {}
+ @desc.each do |desc|
+ desc.pst = self
+ warn "there are duplicate desc records with id #{desc.desc_id}" if @desc_from_id[desc.desc_id]
+ @desc_from_id[desc.desc_id] = desc
+ end
+
+ # now turn the flat list of loaded desc records into a tree
+
+ # well, they have no parent, so they're more like, the toplevel descs.
+ @orphans = []
+ # now assign each node to the parents child array, putting the orphans in the above
+ @desc.each do |desc|
+ parent = @desc_from_id[desc.parent_desc_id]
+ # note, besides this, its possible to create other circular structures.
+ if parent == desc
+ # this actually happens usually, for the root_item it appears.
+ #warn "desc record's parent is itself (#{desc.inspect})"
+ # maybe add some more checks in here for circular structures
+ elsif parent
+ parent.children << desc
+ next
+ end
+ @orphans << desc
+ end
+
+ # maybe change this to some sort of sane-ness check. orphans are expected
+# warn "have #{@orphans.length} orphan desc record(s)." unless @orphans.empty?
+ end
+
+ # load the flat list of desc records recursively
+ #
+ # corresponds to
+ # * _pst_build_desc_ptr
+ # * record_descriptor
+ def load_desc_rec offset, linku1, start_val
+ @desc_offsets << offset
+
+ buf = pst_read_block_size offset, Desc::BLOCK_SIZE, false
+ item_count = buf[ITEM_COUNT_OFFSET]
+
+ # not real desc
+ desc = Desc.new buf[BACKLINK_OFFSET, 4]
+ raise 'blah 1' unless desc.desc_id == linku1
+
+ if buf[LEVEL_INDICATOR_OFFSET] == 0
+ # leaf pointers
+ raise "have too many active items in index (#{item_count})" if item_count > Desc::COUNT_MAX
+ # split the data into item_count desc objects
+ buf[0, Desc::SIZE * item_count].scan(/.{#{Desc::SIZE}}/mo).each_with_index do |data, i|
+ desc = Desc.new data
+ # first entry
+ raise 'blah 3' if i == 0 and start_val != 0 and desc.desc_id != start_val
+ # this shouldn't really happen i'd imagine
+ break if desc.desc_id == 0
+ @desc << desc
+ end
+ else
+ # node pointers
+ raise "have too many active items in index (#{item_count})" if item_count > Index::COUNT_MAX
+ # split the data into item_count table pointers
+ buf[0, TablePtr::SIZE * item_count].scan(/.{#{TablePtr::SIZE}}/mo).each_with_index do |data, i|
+ table = TablePtr.new data
+ # for the first value, we expect the start to be equal note that ids -1, so even for the
+ # first we expect it to be equal. thats the 0x21 (dec 33) desc record. this means we assert
+ # that the first desc record is always 33...
+ raise 'blah 3' if i == 0 and start_val != -1 and table.start != start_val
+ # this shouldn't really happen i'd imagine
+ break if table.start == 0
+ load_desc_rec table.offset, table.u1, table.start
+ end
+ end
+ end
+
+ # as for idx
+ #
+ # corresponds to:
+ # * _pst_getDptr
+ def desc_from_id id
+ @desc_from_id[id]
+ end
+
+ # corresponds to
+ # * pst_load_extended_attributes
+ def load_xattrib
+ unless desc = desc_from_id(0x61)
+ warn "no extended attributes desc record found"
+ return
+ end
+ unless desc.desc
+ warn "no desc idx for extended attributes"
+ return
+ end
+ if desc.list_index
+ end
+ #warn "skipping loading xattribs"
+ # FIXME implement loading xattribs
+ end
+
+ # corresponds to:
+ # * _pst_read_block_size
+ # * _pst_read_block ??
+ # * _pst_ff_getIDblock_dec ??
+ # * _pst_ff_getIDblock ??
+ def pst_read_block_size offset, size, decrypt=true
+ io.seek offset
+ buf = io.read size
+ warn "tried to read #{size} bytes but only got #{buf.length}" if buf.length != size
+ encrypted? && decrypt ? CompressibleEncryption.decrypt(buf) : buf
+ end
+
+ #
+ # id2
+ # ----------------------------------------------------------------------------
+ #
+
+ class ID2Assoc < Struct.new(:id2, :id, :table2)
+ UNPACK_STR = 'V3'
+ SIZE = 12
+
+ def initialize data
+ data = data.unpack(UNPACK_STR) if String === data
+ super(*data)
+ end
+ end
+
+ class ID2Assoc64 < Struct.new(:id2, :u1, :id, :table2)
+ UNPACK_STR = 'VVT2'
+ SIZE = 24
+
+ def initialize data
+ if String === data
+ data = Pst.unpack data, UNPACK_STR
+ end
+ super(*data)
+ end
+
+ def self.load_chain idx
+ buf = idx.read
+ type, count = buf.unpack 'v2'
+ unless type == 0x0002
+ raise 'unknown id2 type 0x%04x' % type
+ #return
+ end
+ id2 = []
+ count.times do |i|
+ assoc = new buf[8 + SIZE * i, SIZE]
+ id2 << assoc
+ if assoc.table2 != 0
+ id2 += load_chain idx.pst.idx_from_id(assoc.table2)
+ end
+ end
+ id2
+ end
+ end
+
+ class ID2Mapping
+ attr_reader :list
+ def initialize pst, list
+ @pst = pst
+ @list = list
+ # create a lookup.
+ @id_from_id2 = {}
+ @list.each do |id2|
+ # NOTE we take the last value seen value if there are duplicates. this "fixes"
+ # test4-o1997.pst for the time being.
+ warn "there are duplicate id2 records with id #{id2.id2}" if @id_from_id2[id2.id2]
+ next if @id_from_id2[id2.id2]
+ @id_from_id2[id2.id2] = id2.id
+ end
+ end
+
+ # TODO: fix logging
+ def warn s
+ Mapi::Log.warn s
+ end
+
+ # corresponds to:
+ # * _pst_getID2
+ def [] id
+ #id2 = @list.find { |x| x.id2 == id }
+ id = @id_from_id2[id]
+ id and @pst.idx_from_id(id)
+ end
+ end
+
+ def load_idx2 idx
+ if header.version_2003?
+ id2 = ID2Assoc64.load_chain idx
+ else
+ id2 = load_idx2_rec idx
+ end
+ ID2Mapping.new self, id2
+ end
+
+ # corresponds to
+ # * _pst_build_id2
+ def load_idx2_rec idx
+ # i should perhaps use a idx chain style read here?
+ buf = pst_read_block_size idx.offset, idx.size, false
+ type, count = buf.unpack 'v2'
+ unless type == 0x0002
+ raise 'unknown id2 type 0x%04x' % type
+ #return
+ end
+ id2 = []
+ count.times do |i|
+ assoc = ID2Assoc.new buf[4 + ID2Assoc::SIZE * i, ID2Assoc::SIZE]
+ id2 << assoc
+ if assoc.table2 != 0
+ id2 += load_idx2_rec idx_from_id(assoc.table2)
+ end
+ end
+ id2
+ end
+
+ class RangesIOIdxChain < RangesIOEncryptable
+ def initialize pst, idx_head
+ @idxs = pst.id2_block_idx_chain idx_head
+ # whether or not a given idx needs encrypting
+ decrypts = @idxs.map do |idx|
+ decrypt = (idx.id & 2) != 0 ? false : pst.encrypted?
+ end.uniq
+ raise NotImplementedError, 'partial encryption in RangesIOID2' if decrypts.length > 1
+ decrypt = decrypts.first
+ # convert idxs to ranges
+ ranges = @idxs.map { |idx| [idx.offset, idx.size] }
+ super pst.io, :ranges => ranges, :decrypt => decrypt
+ end
+ end
+
+ class RangesIOID2 < RangesIOIdxChain
+ def self.new pst, id2, idx2
+ RangesIOIdxChain.new pst, idx2[id2]
+ end
+ end
+
+ # corresponds to:
+ # * _pst_ff_getID2block
+ # * _pst_ff_getID2data
+ # * _pst_ff_compile_ID
+ def id2_block_idx_chain idx
+ if (idx.id & 0x2) == 0
+ [idx]
+ else
+ buf = idx.read
+ type, fdepth, count = buf[0, 4].unpack 'CCv'
+ unless type == 1 # libpst.c:3958
+ warn 'Error in idx_chain - %p, %p, %p - attempting to ignore' % [type, fdepth, count]
+ return [idx]
+ end
+ # there are 4 unaccounted for bytes here, 4...8
+ if header.version_2003?
+ ids = buf[8, count * 8].unpack("T#{count}")
+ else
+ ids = buf[8, count * 4].unpack('V*')
+ end
+ if fdepth == 1
+ ids.map { |id| idx_from_id id }
+ else
+ ids.map { |id| id2_block_idx_chain idx_from_id(id) }.flatten
+ end
+ end
+ end
+
+ #
+ # main block parsing code. gets raw properties
+ # ----------------------------------------------------------------------------
+ #
+
+ # the job of this class, is to take a desc record, and be able to enumerate through the
+ # mapi properties of the associated thing.
+ #
+ # corresponds to
+ # * _pst_parse_block
+ # * _pst_process (in some ways. although perhaps thats more the Item::Properties#add_property)
+ class BlockParser
+ include Mapi::Types::Constants
+
+ TYPES = {
+ 0xbcec => 1,
+ 0x7cec => 2,
+ # type 3 is removed. an artifact of not handling the indirect blocks properly in libpst.
+ }
+
+ PR_SUBJECT = PropertySet::TAGS.find { |num, (name, type)| name == 'PR_SUBJECT' }.first.hex
+ PR_BODY_HTML = PropertySet::TAGS.find { |num, (name, type)| name == 'PR_BODY_HTML' }.first.hex
+
+ # this stuff could maybe be moved to Ole::Types? or leverage it somehow?
+ # whether or not a type is immeidate is more a property of the pst encoding though i expect.
+ # what i probably can add is a generic concept of whether a type is of variadic length or not.
+
+ # these lists are very incomplete. think they are largely copied from libpst
+
+ IMMEDIATE_TYPES = [
+ PT_SHORT, PT_LONG, PT_BOOLEAN
+ ]
+
+ INDIRECT_TYPES = [
+ PT_DOUBLE, PT_OBJECT,
+ 0x0014, # whats this? probably something like PT_LONGLONG, given the correspondence with the
+ # ole variant types. (= VT_I8)
+ PT_STRING8, PT_UNICODE, # unicode isn't in libpst, but added here for outlook 2003 down the track
+ PT_SYSTIME,
+ 0x0048, # another unknown
+ 0x0102, # this is PT_BINARY vs PT_CLSID
+ #0x1003, # these are vector types, but they're commented out for now because i'd expect that
+ #0x1014, # there's extra decoding needed that i'm not doing. (probably just need a simple
+ # # PT_* => unpack string mapping for the immediate types, and just do unpack('V*') etc
+ #0x101e,
+ #0x1102
+ ]
+
+ # the attachment and recipient arrays appear to be always stored with these fixed
+ # id2 values. seems strange. are there other extra streams? can find out by making higher
+ # level IO wrapper, which has the id2 value, and doing the diff of available id2 values versus
+ # used id2 values in properties of an item.
+ ID2_ATTACHMENTS = 0x671
+ ID2_RECIPIENTS = 0x692
+
+ attr_reader :desc, :data, :data_chunks, :offset_tables
+ def initialize desc
+ raise FormatError, "unable to get associated index record for #{desc.inspect}" unless desc.desc
+ @desc = desc
+ #@data = desc.desc.read
+ if Pst::Index === desc.desc
+ #@data = RangesIOIdxChain.new(desc.pst, desc.desc).read
+ idxs = desc.pst.id2_block_idx_chain desc.desc
+ # this gets me the plain index chain.
+ else
+ # fake desc
+ #@data = desc.desc.read
+ idxs = [desc.desc]
+ end
+
+ @data_chunks = idxs.map { |idx| idx.read }
+ @data = @data_chunks.first
+
+ load_header
+
+ @index_offsets = [@index_offset] + @data_chunks[1..-1].map { |chunk| chunk.unpack('v')[0] }
+ @offset_tables = []
+ @ignored = []
+ @data_chunks.zip(@index_offsets).each do |chunk, offset|
+ ignore = chunk[offset, 2].unpack('v')[0]
+ @ignored << ignore
+# p ignore
+ @offset_tables.push offset_table = []
+ # maybe its ok if there aren't to be any values ?
+ raise FormatError if offset == 0
+ offsets = chunk[offset + 2..-1].unpack('v*')
+ #p offsets
+ offsets[0, ignore + 2].each_cons 2 do |from, to|
+ #next if to == 0
+ raise FormatError, [from, to].inspect if from > to
+ offset_table << [from, to]
+ end
+ end
+
+ @offset_table = @offset_tables.first
+ @idxs = idxs
+
+ # now, we may have multiple different blocks
+ end
+
+ # a given desc record may or may not have associated idx2 data. we lazily load it here, so it will never
+ # actually be requested unless get_data_indirect actually needs to use it.
+ def idx2
+ return @idx2 if @idx2
+ raise FormatError, 'idx2 requested but no idx2 available' unless desc.list_index
+ # should check this can't return nil
+ @idx2 = desc.pst.load_idx2 desc.list_index
+ end
+
+ def load_header
+ @index_offset, type, @offset1 = data.unpack 'vvV'
+ raise FormatError, 'unknown block type signature 0x%04x' % type unless TYPES[type]
+ @type = TYPES[type]
+ end
+
+ # based on the value of offset, return either some data from buf, or some data from the
+ # id2 chain id2, where offset is some key into a lookup table that is stored as the id2
+ # chain. i think i may need to create a BlockParser class that wraps up all this mess.
+ #
+ # corresponds to:
+ # * _pst_getBlockOffsetPointer
+ # * _pst_getBlockOffset
+ def get_data_indirect offset
+ return get_data_indirect_io(offset).read
+
+ if offset == 0
+ nil
+ elsif (offset & 0xf) == 0xf
+ RangesIOID2.new(desc.pst, offset, idx2).read
+ else
+ low, high = offset & 0xf, offset >> 4
+ raise FormatError if low != 0 or (high & 0x1) != 0 or (high / 2) > @offset_table.length
+ from, to = @offset_table[high / 2]
+ data[from...to]
+ end
+ end
+
+ def get_data_indirect_io offset
+ if offset == 0
+ nil
+ elsif (offset & 0xf) == 0xf
+ if idx2[offset]
+ RangesIOID2.new desc.pst, offset, idx2
+ else
+ warn "tried to get idx2 record for #{offset} but failed"
+ return StringIO.new('')
+ end
+ else
+ low, high = offset & 0xf, offset >> 4
+ if low != 0 or (high & 0x1) != 0
+# raise FormatError,
+ warn "bad - #{low} #{high} (1)"
+ return StringIO.new('')
+ end
+ # lets see which block it should come from.
+ block_idx, i = high.divmod 4096
+ unless block_idx < @data_chunks.length
+ warn "bad - block_idx to high (not #{block_idx} < #{@data_chunks.length})"
+ return StringIO.new('')
+ end
+ data_chunk, offset_table = @data_chunks[block_idx], @offset_tables[block_idx]
+ if i / 2 >= offset_table.length
+ warn "bad - #{low} #{high} - #{i / 2} >= #{offset_table.length} (2)"
+ return StringIO.new('')
+ end
+ #warn "ok - #{low} #{high} #{offset_table.length}"
+ from, to = offset_table[i / 2]
+ StringIO.new data_chunk[from...to]
+ end
+ end
+
+ def handle_indirect_values key, type, value
+ case type
+ when PT_BOOLEAN
+ value = value != 0
+ when *IMMEDIATE_TYPES # not including PT_BOOLEAN which we just did above
+ # no processing current applied (needed?).
+ when *INDIRECT_TYPES
+ # the value is a pointer
+ if String === value # ie, value size > 4 above
+ value = StringIO.new value
+ else
+ value = get_data_indirect_io(value)
+ end
+ # keep strings as immediate values for now, for compatability with how i set up
+ # Msg::Properties::ENCODINGS
+ if value
+ if type == PT_STRING8
+ value = value.read
+ elsif type == PT_UNICODE
+ value = Ole::Types::FROM_UTF16.iconv value.read
+ end
+ end
+ # special subject handling
+ if key == PR_BODY_HTML and value
+ # to keep the msg code happy, which thinks body_html will be an io
+ # although, in 2003 version, they are 0102 already
+ value = StringIO.new value unless value.respond_to?(:read)
+ end
+ if key == PR_SUBJECT and value
+ ignore, offset = value.unpack 'C2'
+ offset = (offset == 1 ? nil : offset - 3)
+ value = value[2..-1]
+=begin
+ index = value =~ /^[A-Z]*:/ ? $~[0].length - 1 : nil
+ unless ignore == 1 and offset == index
+ warn 'something wrong with subject hack'
+ $x = [ignore, offset, value]
+ require 'irb'
+ IRB.start
+ exit
+ end
+=end
+=begin
+new idea:
+
+making sense of the \001\00[156] i've seen prefixing subject. i think its to do with the placement
+of the ':', or the ' '. And perhaps an optimization to do with thread topic, and ignoring the prefixes
+added by mailers. thread topic is equal to subject with all that crap removed.
+
+can test by creating some mails with bizarre subjects.
+
+subject="\001\005RE: blah blah"
+subject="\001\001blah blah"
+subject="\001\032Out of Office AutoReply: blah blah"
+subject="\001\020Undeliverable: blah blah"
+
+looks like it
+
+=end
+
+ # now what i think, is that perhaps, value[offset..-1] ...
+ # or something like that should be stored as a special tag. ie, do a double yield
+ # for this case. probably PR_CONVERSATION_TOPIC, in which case i'd write instead:
+ # yield [PR_SUBJECT, ref_type, value]
+ # yield [PR_CONVERSATION_TOPIC, ref_type, value[offset..-1]
+ # next # to skip the yield.
+ end
+
+ # special handling for embedded objects
+ # used for attach_data for attached messages. in which case attach_method should == 5,
+ # for embedded object.
+ if type == PT_OBJECT and value
+ value = value.read if value.respond_to?(:read)
+ id2, unknown = value.unpack 'V2'
+ io = RangesIOID2.new desc.pst, id2, idx2
+
+ # hacky
+ desc2 = OpenStruct.new(:desc => io, :pst => desc.pst, :list_index => desc.list_index, :children => [])
+ # put nil instead of desc.list_index, otherwise the attachment is attached to itself ad infinitum.
+ # should try and fix that FIXME
+ # this shouldn't be done always. for an attached message, yes, but for an attached
+ # meta file, for example, it shouldn't. difference between embedded_ole vs embedded_msg
+ # really.
+ # note that in the case where its a embedded ole, you actually get a regular serialized ole
+ # object, so i need to create an ole storage object on a rangesioidxchain!
+ # eg:
+=begin
+att.props.display_name # => "Picture (Metafile)"
+io = att.props.attach_data
+io.read(32).unpack('H*') # => ["d0cf11e0a1b11ae100000.... note the docfile signature.
+# plug some missing rangesio holes:
+def io.rewind; seek 0; end
+def io.flush; raise IOError; end
+ole = Ole::Storage.open io
+puts ole.root.to_tree
+
+- #<Dirent:"Root Entry">
+ |- #<Dirent:"\001Ole" size=20 data="\001\000\000\002\000...">
+ |- #<Dirent:"CONTENTS" size=65696 data="\327\315\306\232\000...">
+ \- #<Dirent:"\003MailStream" size=12 data="\001\000\000\000[...">
+=end
+ # until properly fixed, i have disabled this code here, so this will break
+ # nested messages temporarily.
+ #value = Item.new desc2, RawPropertyStore.new(desc2).to_a
+ #desc2.list_index = nil
+ value = io
+ end
+ # this is PT_MV_STRING8, i guess.
+ # should probably have the 0x1000 flag, and do the or-ring.
+ # example of 0x1102 is PR_OUTLOOK_2003_ENTRYIDS. less sure about that one.
+ when 0x101e, 0x1102
+ # example data:
+ # 0x802b "\003\000\000\000\020\000\000\000\030\000\000\000#\000\000\000BusinessCompetitionFavorites"
+ # this 0x802b would be an extended attribute for categories / keywords.
+ value = get_data_indirect_io(value).read unless String === value
+ num = value.unpack('V')[0]
+ offsets = value[4, 4 * num].unpack("V#{num}")
+ value = (offsets + [value.length]).to_enum(:each_cons, 2).map { |from, to| value[from...to] }
+ value.map! { |str| StringIO.new str } if type == 0x1102
+ else
+ name = Mapi::Types::DATA[type].first rescue nil
+ warn '0x%04x %p' % [key, get_data_indirect_io(value).read]
+ raise NotImplementedError, 'unsupported mapi property type - 0x%04x (%p)' % [type, name]
+ end
+ [key, type, value]
+ end
+ end
+
+=begin
+* recipients:
+
+ affects: ["0x200764", "0x2011c4", "0x201b24", "0x201b44", "0x201ba4", "0x201c24", "0x201cc4", "0x202504"]
+
+after adding the rawpropertystoretable fix, all except the second parse properly, and satisfy:
+
+ item.props.display_to == item.recipients.map { |r| r.props.display_name if r.props.recipient_type == 1 }.compact * '; '
+
+only the second still has a problem
+
+#[#<struct Pst::Desc desc_id=0x2011c4, idx_id=0x397c, idx2_id=0x398a, parent_desc_id=0x8082>]
+
+think this is related to a multi block #data3. ie, when you use @x * rec_size, and it
+goes > 8190, or there abouts, then it stuffs up. probably there is header gunk, or something,
+similar to when #data is multi block.
+
+same problem affects the attachment table in test4.
+
+fixed that issue. round data3 ranges to rec_size.
+
+fix other issue with attached objects.
+
+all recipients and attachments in test2 are fine.
+
+only remaining issue is test4 recipients of 200044. strange.
+
+=end
+
+ # RawPropertyStore is used to iterate through the properties of an item, or the auxiliary
+ # data for an attachment. its just a parser for the way the properties are serialized, when the
+ # properties don't have to conform to a column structure.
+ #
+ # structure of this chunk of data is often
+ # header, property keys, data values, and then indexes.
+ # the property keys has value in it. value can be the actual value if its a short type,
+ # otherwise you lookup the value in the indicies, where you get the offsets to use in the
+ # main data body. due to the indirect thing though, any of these parts could actually come
+ # from a separate stream.
+ class RawPropertyStore < BlockParser
+ include Enumerable
+
+ attr_reader :length
+ def initialize desc
+ super
+ raise FormatError, "expected type 1 - got #{@type}" unless @type == 1
+
+ # the way that offset works, data1 may be a subset of buf, or something from id2. if its from buf,
+ # it will be offset based on index_offset and offset. so it could be some random chunk of data anywhere
+ # in the thing.
+ header_data = get_data_indirect @offset1
+ raise FormatError if header_data.length < 8
+ signature, offset2 = header_data.unpack 'V2'
+ #p [@type, signature]
+ raise FormatError, 'unhandled block signature 0x%08x' % @type if signature != 0x000602b5
+ # this is actually a big chunk of tag tuples.
+ @index_data = get_data_indirect offset2
+ @length = @index_data.length / 8
+ end
+
+ # iterate through the property tuples
+ def each
+ length.times do |i|
+ key, type, value = handle_indirect_values(*@index_data[8 * i, 8].unpack('vvV'))
+ yield key, type, value
+ end
+ end
+ end
+
+ # RawPropertyStoreTable is kind of like a database table.
+ # it has a fixed set of columns.
+ # #[] is kind of like getting a row from the table.
+ # those rows are currently encapsulated by Row, which has #each like
+ # RawPropertyStore.
+ # only used for the recipients array, and the attachments array. completely lazy, doesn't
+ # load any of the properties upon creation.
+ class RawPropertyStoreTable < BlockParser
+ class Column < Struct.new(:ref_type, :type, :ind2_off, :size, :slot)
+ def initialize data
+ super(*data.unpack('v3CC'))
+ end
+
+ def nice_type_name
+ Mapi::Types::DATA[ref_type].first[/_(.*)/, 1].downcase rescue '0x%04x' % ref_type
+ end
+
+ def nice_prop_name
+ Mapi::PropertyStore::TAGS['%04x' % type].first[/_(.*)/, 1].downcase rescue '0x%04x' % type
+ end
+
+ def inspect
+ "#<#{self.class} name=#{nice_prop_name.inspect}, type=#{nice_type_name.inspect}>"
+ end
+ end
+
+ include Enumerable
+
+ attr_reader :length, :index_data, :data2, :data3, :rec_size
+ def initialize desc
+ super
+ raise FormatError, "expected type 2 - got #{@type}" unless @type == 2
+
+ header_data = get_data_indirect @offset1
+ # seven_c_blk
+ # often: u1 == u2 and u3 == u2 + 2, then rec_size == u3 + 4. wtf
+ seven_c, @num_list, u1, u2, u3, @rec_size, b_five_offset,
+ ind2_offset, u7, u8 = header_data[0, 22].unpack('CCv4V2v2')
+ @index_data = header_data[22..-1]
+
+ raise FormatError if @num_list != schema.length or seven_c != 0x7c
+ # another check
+ min_size = schema.inject(0) { |total, col| total + col.size }
+ # seem to have at max, 8 padding bytes on the end of the record. not sure if it means
+ # anything. maybe its just space that hasn't been reclaimed due to columns being
+ # removed or something. probably should just check lower bound.
+ range = (min_size..min_size + 8)
+ warn "rec_size seems wrong (#{range} !=== #{rec_size})" unless range === rec_size
+
+ header_data2 = get_data_indirect b_five_offset
+ raise FormatError if header_data2.length < 8
+ signature, offset2 = header_data2.unpack 'V2'
+ # ??? seems a bit iffy
+ # there's probably more to the differences than this, and the data2 difference below
+ expect = desc.pst.header.version_2003? ? 0x000404b5 : 0x000204b5
+ raise FormatError, 'unhandled block signature 0x%08x' % signature if signature != expect
+
+ # this holds all the row data
+ # handle multiple block issue.
+ @data3_io = get_data_indirect_io ind2_offset
+ if RangesIOIdxChain === @data3_io
+ @data3_idxs =
+ # modify ranges
+ ranges = @data3_io.ranges.map { |offset, size| [offset, size / @rec_size * @rec_size] }
+ @data3_io.instance_variable_set :@ranges, ranges
+ end
+ @data3 = @data3_io.read
+
+ # there must be something to the data in data2. i think data2 is the array of objects essentially.
+ # currently its only used to imply a length
+ # actually, at size 6, its just some auxiliary data. i'm thinking either Vv/vV, for 97, and something
+ # wider for 03. the second value is just the index (0...length), and the first value is
+ # some kind of offset i expect. actually, they were all id2 values, in another case.
+ # so maybe they're get_data_indirect values too?
+ # actually, it turned out they were identical to the PR_ATTACHMENT_ID2 values...
+ # id2_values = ie, data2.unpack('v*').to_enum(:each_slice, 3).transpose[0]
+ # table[i].assoc(PR_ATTACHMENT_ID2).last == id2_values[i], for all i.
+ @data2 = get_data_indirect(offset2) rescue nil
+ #if data2
+ # @length = (data2.length / 6.0).ceil
+ #else
+ # the above / 6, may have been ok for 97 files, but the new 0x0004 style block must have
+ # different size records... just use this instead:
+ # hmmm, actually, we can still figure it out:
+ @length = @data3.length / @rec_size
+ #end
+
+ # lets try and at least use data2 for a warning for now
+ if data2
+ data2_rec_size = desc.pst.header.version_2003? ? 8 : 6
+ warn 'somthing seems wrong with data3' unless @length == (data2.length / data2_rec_size)
+ end
+ end
+
+ def schema
+ @schema ||= index_data.scan(/.{8}/m).map { |data| Column.new data }
+ end
+
+ def [] idx
+ # handle funky rounding
+ Row.new self, idx * @rec_size
+ end
+
+ def each
+ length.times { |i| yield self[i] }
+ end
+
+ class Row
+ include Enumerable
+
+ def initialize array_parser, x
+ @array_parser, @x = array_parser, x
+ end
+
+ # iterate through the property tuples
+ def each
+ (@array_parser.index_data.length / 8).times do |i|
+ ref_type, type, ind2_off, size, slot = @array_parser.index_data[8 * i, 8].unpack 'v3CC'
+ # check this rescue too
+ value = @array_parser.data3[@x + ind2_off, size]
+# if INDIRECT_TYPES.include? ref_type
+ if size <= 4
+ value = value.unpack('V')[0]
+ end
+ #p ['0x%04x' % ref_type, '0x%04x' % type, (Msg::Properties::MAPITAGS['%04x' % type].first[/^.._(.*)/, 1].downcase rescue nil),
+ # value_orig, value, (get_data_indirect(value_orig.unpack('V')[0]) rescue nil), size, ind2_off, slot]
+ key, type, value = @array_parser.handle_indirect_values type, ref_type, value
+ yield key, type, value
+ end
+ end
+ end
+ end
+
+ class AttachmentTable < BlockParser
+ # a "fake" MAPI property name for this constant. if you get a mapi property with
+ # this value, it is the id2 value to use to get attachment data.
+ PR_ATTACHMENT_ID2 = 0x67f2
+
+ attr_reader :desc, :table
+ def initialize desc
+ @desc = desc
+ # no super, we only actually want BlockParser2#idx2
+ @table = nil
+ return unless desc.list_index
+ return unless idx = idx2[ID2_ATTACHMENTS]
+ # FIXME make a fake desc.
+ @desc2 = OpenStruct.new :desc => idx, :pst => desc.pst, :list_index => desc.list_index
+ @table = RawPropertyStoreTable.new @desc2
+ end
+
+ def to_a
+ return [] if !table
+ table.map do |attachment|
+ attachment = attachment.to_a
+ #p attachment
+ # potentially merge with yet more properties
+ # this still seems pretty broken - especially the property overlap
+ if attachment_id2 = attachment.assoc(PR_ATTACHMENT_ID2)
+ #p attachment_id2.last
+ #p idx2[attachment_id2.last]
+ @desc2.desc = idx2[attachment_id2.last]
+ RawPropertyStore.new(@desc2).each do |a, b, c|
+ record = attachment.assoc a
+ attachment << record = [] unless record
+ record.replace [a, b, c]
+ end
+ end
+ attachment
+ end
+ end
+ end
+
+ # there is no equivalent to this in libpst. ID2_RECIPIENTS was just guessed given the above
+ # AttachmentTable.
+ class RecipientTable < BlockParser
+ attr_reader :desc, :table
+ def initialize desc
+ @desc = desc
+ # no super, we only actually want BlockParser2#idx2
+ @table = nil
+ return unless desc.list_index
+ return unless idx = idx2[ID2_RECIPIENTS]
+ # FIXME make a fake desc.
+ desc2 = OpenStruct.new :desc => idx, :pst => desc.pst, :list_index => desc.list_index
+ @table = RawPropertyStoreTable.new desc2
+ end
+
+ def to_a
+ return [] if !table
+ table.map { |x| x.to_a }
+ end
+ end
+
+ #
+ # higher level item code. wraps up the raw properties above, and gives nice
+ # objects to work with. handles item relationships too.
+ # ----------------------------------------------------------------------------
+ #
+
+ def self.make_property_set property_list
+ hash = property_list.inject({}) do |hash, (key, type, value)|
+ hash.update PropertySet::Key.new(key) => value
+ end
+ PropertySet.new hash
+ end
+
+ class Attachment < Mapi::Attachment
+ def initialize list
+ super Pst.make_property_set(list)
+
+ @embedded_msg = props.attach_data if Item === props.attach_data
+ end
+ end
+
+ class Recipient < Mapi::Recipient
+ def initialize list
+ super Pst.make_property_set(list)
+ end
+ end
+
+ class Item < Mapi::Message
+ class EntryID < Struct.new(:u1, :entry_id, :id)
+ UNPACK_STR = 'VA16V'
+
+ def initialize data
+ data = data.unpack(UNPACK_STR) if String === data
+ super(*data)
+ end
+ end
+
+ include RecursivelyEnumerable
+
+ attr_accessor :type, :parent
+
+ def initialize desc, list, type=nil
+ @desc = desc
+ super Pst.make_property_set(list)
+
+ # this is kind of weird, but the ids of the special folders are stored in a hash
+ # when the root item is loaded
+ if ipm_wastebasket_entryid
+ desc.pst.special_folder_ids[ipm_wastebasket_entryid] = :wastebasket
+ end
+
+ if finder_entryid
+ desc.pst.special_folder_ids[finder_entryid] = :finder
+ end
+
+ # and then here, those are used, along with a crappy heuristic to determine if we are an
+ # item
+=begin
+i think the low bits of the desc_id can give some info on the type.
+
+it seems that 0x4 is for regular messages (and maybe contacts etc)
+0x2 is for folders, and 0x8 is for special things like rules etc, that aren't visible.
+=end
+ unless type
+ type = props.valid_folder_mask || ipm_subtree_entryid || props.content_count || props.subfolders ? :folder : :message
+ if type == :folder
+ type = desc.pst.special_folder_ids[desc.desc_id] || type
+ end
+ end
+
+ @type = type
+ end
+
+ def each_child
+ id = ipm_subtree_entryid
+ if id
+ root = @desc.pst.desc_from_id id
+ raise "couldn't find root" unless root
+ raise 'both kinds of children' unless @desc.children.empty?
+ children = root.children
+ # lets look up the other ids we have.
+ # typically the wastebasket one "deleted items" is in the children already, but
+ # the search folder isn't.
+ extras = [ipm_wastebasket_entryid, finder_entryid].compact.map do |id|
+ root = @desc.pst.desc_from_id id
+ warn "couldn't find root for id #{id}" unless root
+ root
+ end.compact
+ # i do this instead of union, so as not to mess with the order of the
+ # existing children.
+ children += (extras - children)
+ children
+ else
+ @desc.children
+ end.each do |desc|
+ item = @desc.pst.pst_parse_item(desc)
+ item.parent = self
+ yield item
+ end
+ end
+
+ def path
+ parents, item = [], self
+ parents.unshift item while item = item.parent
+ # remove root
+ parents.shift
+ parents.map { |item| item.props.display_name or raise 'unable to construct path' } * '/'
+ end
+
+ def children
+ to_enum(:each_child).to_a
+ end
+
+ # these are still around because they do different stuff
+
+ # Top of Personal Folder Record
+ def ipm_subtree_entryid
+ @ipm_subtree_entryid ||= EntryID.new(props.ipm_subtree_entryid.read).id rescue nil
+ end
+
+ # Deleted Items Folder Record
+ def ipm_wastebasket_entryid
+ @ipm_wastebasket_entryid ||= EntryID.new(props.ipm_wastebasket_entryid.read).id rescue nil
+ end
+
+ # Search Root Record
+ def finder_entryid
+ @finder_entryid ||= EntryID.new(props.finder_entryid.read).id rescue nil
+ end
+
+ # all these have been replaced with the method_missing below
+=begin
+ # States which folders are valid for this message store
+ #def valid_folder_mask
+ # props[0x35df]
+ #end
+
+ # Number of emails stored in a folder
+ def content_count
+ props[0x3602]
+ end
+
+ # Has children
+ def subfolders
+ props[0x360a]
+ end
+=end
+
+ # i think i will change these, so they can inherit the lazyness from RawPropertyStoreTable.
+ # so if you want the last attachment, you can get it without creating the others perhaps.
+ # it just has to handle the no table at all case a bit more gracefully.
+
+ def attachments
+ @attachments ||= AttachmentTable.new(@desc).to_a.map { |list| Attachment.new list }
+ end
+
+ def recipients
+ #[]
+ @recipients ||= RecipientTable.new(@desc).to_a.map { |list| Recipient.new list }
+ end
+
+ def each_recursive(&block)
+ #p :self => self
+ children.each do |child|
+ #p :child => child
+ block[child]
+ child.each_recursive(&block)
+ end
+ end
+
+ def inspect
+ attrs = %w[display_name subject sender_name subfolders]
+# attrs = %w[display_name valid_folder_mask ipm_wastebasket_entryid finder_entryid content_count subfolders]
+ str = attrs.map { |a| b = props.send a; " #{a}=#{b.inspect}" if b }.compact * ','
+
+ type_s = type == :message ? 'Message' : type == :folder ? 'Folder' : type.to_s.capitalize + 'Folder'
+ str2 = 'desc_id=0x%x' % @desc.desc_id
+
+ !str.empty? ? "#<Pst::#{type_s} #{str2}#{str}>" : "#<Pst::#{type_s} #{str2} props=#{props.inspect}>" #\n" + props.transport_message_headers + ">"
+ end
+ end
+
+ # corresponds to
+ # * _pst_parse_item
+ def pst_parse_item desc
+ Item.new desc, RawPropertyStore.new(desc).to_a
+ end
+
+ #
+ # other random code
+ # ----------------------------------------------------------------------------
+ #
+
+ def dump_debug_info
+ puts "* pst header"
+ p header
+
+=begin
+Looking at the output of this, for blank-o1997.pst, i see this part:
+...
+- (26624,516) desc block data (overlap of 4 bytes)
+- (27136,516) desc block data (gap of 508 bytes)
+- (28160,516) desc block data (gap of 2620 bytes)
+...
+
+which confirms my belief that the block size for idx and desc is more likely 512
+=end
+ if 0 + 0 == 0
+ puts '* file range usage'
+ file_ranges =
+ # these 3 things, should account for most of the data in the file.
+ [[0, Header::SIZE, 'pst file header']] +
+ @idx_offsets.map { |offset| [offset, Index::BLOCK_SIZE, 'idx block data'] } +
+ @desc_offsets.map { |offset| [offset, Desc::BLOCK_SIZE, 'desc block data'] } +
+ @idx.map { |idx| [idx.offset, idx.size, 'idx id=0x%x (%s)' % [idx.id, idx.type]] }
+ (file_ranges.sort_by { |idx| idx.first } + [nil]).to_enum(:each_cons, 2).each do |(offset, size, name), next_record|
+ # i think there is a padding of the size out to 64 bytes
+ # which is equivalent to padding out the final offset, because i think the offset is
+ # similarly oriented
+ pad_amount = 64
+ warn 'i am wrong about the offset padding' if offset % pad_amount != 0
+ # so, assuming i'm not wrong about that, then we can calculate how much padding is needed.
+ pad = pad_amount - (size % pad_amount)
+ pad = 0 if pad == pad_amount
+ gap = next_record ? next_record.first - (offset + size + pad) : 0
+ extra = case gap <=> 0
+ when -1; ["overlap of #{gap.abs} bytes)"]
+ when 0; []
+ when +1; ["gap of #{gap} bytes"]
+ end
+ # how about we check that padding
+ @io.pos = offset + size
+ pad_bytes = @io.read(pad)
+ extra += ["padding not all zero"] unless pad_bytes == 0.chr * pad
+ puts "- #{offset}:#{size}+#{pad} #{name.inspect}" + (extra.empty? ? '' : ' [' + extra * ', ' + ']')
+ end
+ end
+
+ # i think the idea of the idx, and indeed the idx2, is just to be able to
+ # refer to data indirectly, which means it can get moved around, and you just update
+ # the idx table. it is simply a list of file offsets and sizes.
+ # not sure i get how id2 plays into it though....
+ # the sizes seem to be all even. is that a co-incidence? and the ids are all even. that
+ # seems to be related to something else (see the (id & 2) == 1 stuff)
+ puts '* idx entries'
+ @idx.each { |idx| puts "- #{idx.inspect}" }
+
+ # if you look at the desc tree, you notice a few things:
+ # 1. there is a desc that seems to be the parent of all the folders, messages etc.
+ # it is the one whose parent is itself.
+ # one of its children is referenced as the subtree_entryid of the first desc item,
+ # the root.
+ # 2. typically only 2 types of desc records have idx2_id != 0. messages themselves,
+ # and the desc with id = 0x61 - the xattrib container. everything else uses the
+ # regular ids to find its data. i think it should be reframed as small blocks and
+ # big blocks, but i'll look into it more.
+ #
+ # idx_id and idx2_id are for getting to the data. desc_id and parent_desc_id just define
+ # the parent <-> child relationship, and the desc_ids are how the items are referred to in
+ # entryids.
+ # note that these aren't unique! eg for 0, 4 etc. i expect these'd never change, as the ids
+ # are stored in entryids. whereas the idx and idx2 could be a bit more volatile.
+ puts '* desc tree'
+ # make a dummy root hold everything just for convenience
+ root = Desc.new ''
+ def root.inspect; "#<Pst::Root>"; end
+ root.children.replace @orphans
+ # this still loads the whole thing as a string for gsub. should use directo output io
+ # version.
+ puts root.to_tree.gsub(/, (parent_desc_id|idx2_id)=0x0(?!\d)/, '')
+
+ # this is fairly easy to understand, its just an attempt to display the pst items in a tree form
+ # which resembles what you'd see in outlook.
+ puts '* item tree'
+ # now streams directly
+ root_item.to_tree STDOUT
+ end
+
+ def root_desc
+ @desc.first
+ end
+
+ def root_item
+ item = pst_parse_item root_desc
+ item.type = :root
+ item
+ end
+
+ def root
+ root_item
+ end
+
+ # depth first search of all items
+ include Enumerable
+
+ def each(&block)
+ root = self.root
+ block[root]
+ root.each_recursive(&block)
+ end
+
+ def name
+ @name ||= root_item.props.display_name
+ end
+
+ def inspect
+ "#<Pst name=#{name.inspect} io=#{io.inspect}>"
+ end
+end
+end
+
diff --git a/vendor/ruby-msg/lib/mapi/rtf.rb b/vendor/ruby-msg/lib/mapi/rtf.rb
new file mode 100644
index 000000000..9fa133fac
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi/rtf.rb
@@ -0,0 +1,169 @@
+require 'stringio'
+require 'strscan'
+require 'rtf'
+
+module Mapi
+ #
+ # = Introduction
+ #
+ # The +RTF+ module contains a few helper functions for dealing with rtf
+ # in mapi messages: +rtfdecompr+, and <tt>rtf2html</tt>.
+ #
+ # Both were ported from their original C versions for simplicity's sake.
+ #
+ module RTF
+ RTF_PREBUF =
+ "{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}" \
+ "{\\f0\\fnil \\froman \\fswiss \\fmodern \\fscript " \
+ "\\fdecor MS Sans SerifSymbolArialTimes New RomanCourier" \
+ "{\\colortbl\\red0\\green0\\blue0\n\r\\par " \
+ "\\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx"
+
+ # Decompresses compressed rtf +data+, as found in the mapi property
+ # +PR_RTF_COMPRESSED+. Code converted from my C version, which in turn
+ # I wrote from a Java source, in JTNEF I believe.
+ #
+ # C version was modified to use circular buffer for back references,
+ # instead of the optimization of the Java version to index directly into
+ # output buffer. This was in preparation to support streaming in a
+ # read/write neutral fashion.
+ def rtfdecompr data
+ io = StringIO.new data
+ buf = RTF_PREBUF + "\x00" * (4096 - RTF_PREBUF.length)
+ wp = RTF_PREBUF.length
+ rtf = ''
+
+ # get header fields (as defined in RTFLIB.H)
+ compr_size, uncompr_size, magic, crc32 = io.read(16).unpack 'V*'
+ #warn "compressed-RTF data size mismatch" unless io.size == data.compr_size + 4
+
+ # process the data
+ case magic
+ when 0x414c454d # "MELA" magic number that identifies the stream as a uncompressed stream
+ rtf = io.read uncompr_size
+ when 0x75465a4c # "LZFu" magic number that identifies the stream as a compressed stream
+ flag_count = -1
+ flags = nil
+ while rtf.length < uncompr_size and !io.eof?
+ # each flag byte flags 8 literals/references, 1 per bit
+ flags = ((flag_count += 1) % 8 == 0) ? io.getc : flags >> 1
+ if 1 == (flags & 1) # each flag bit is 1 for reference, 0 for literal
+ rp, l = io.getc, io.getc
+ # offset is a 12 byte number. 2^12 is 4096, so thats fine
+ rp = (rp << 4) | (l >> 4) # the offset relative to block start
+ l = (l & 0xf) + 2 # the number of bytes to copy
+ l.times do
+ rtf << buf[wp] = buf[rp]
+ wp = (wp + 1) % 4096
+ rp = (rp + 1) % 4096
+ end
+ else
+ rtf << buf[wp] = io.getc
+ wp = (wp + 1) % 4096
+ end
+ end
+ else # unknown magic number
+ raise "Unknown compression type (magic number 0x%08x)" % magic
+ end
+
+ # not sure if its due to a bug in the above code. doesn't seem to be
+ # in my tests, but sometimes there's a trailing null. we chomp it here,
+ # which actually makes the resultant rtf smaller than its advertised
+ # size (+uncompr_size+).
+ rtf.chomp! 0.chr
+ rtf
+ end
+
+ # Note, this is a conversion of the original C code. Not great - needs tests and
+ # some refactoring, and an attempt to correct some inaccuracies. Hacky but works.
+ #
+ # Returns +nil+ if it doesn't look like an rtf encapsulated rtf.
+ #
+ # Some cases that the original didn't deal with have been patched up, eg from
+ # this chunk, where there are tags outside of the htmlrtf ignore block.
+ #
+ # "{\\*\\htmltag116 <br />}\\htmlrtf \\line \\htmlrtf0 \\line {\\*\\htmltag84 <a href..."
+ #
+ # We take the approach of ignoring all rtf tags not explicitly handled. A proper
+ # parse tree would be nicer to work with. will need to look for ruby rtf library
+ #
+ # Some of the original comment to the c code is excerpted here:
+ #
+ # Sometimes in MAPI, the PR_BODY_HTML property contains the HTML of a message.
+ # But more usually, the HTML is encoded inside the RTF body (which you get in the
+ # PR_RTF_COMPRESSED property). These routines concern the decoding of the HTML
+ # from this RTF body.
+ #
+ # An encoded htmlrtf file is a valid RTF document, but which contains additional
+ # html markup information in its comments, and sometimes contains the equivalent
+ # rtf markup outside the comments. Therefore, when it is displayed by a plain
+ # simple RTF reader, the html comments are ignored and only the rtf markup has
+ # effect. Typically, this rtf markup is not as rich as the html markup would have been.
+ # But for an html-aware reader (such as the code below), we can ignore all the
+ # rtf markup, and extract the html markup out of the comments, and get a valid
+ # html document.
+ #
+ # There are actually two kinds of html markup in comments. Most of them are
+ # prefixed by "\*\htmltagNNN", for some number NNN. But sometimes there's one
+ # prefixed by "\*\mhtmltagNNN" followed by "\*\htmltagNNN". In this case,
+ # the two are equivalent, but the m-tag is for a MIME Multipart/Mixed Message
+ # and contains tags that refer to content-ids (e.g. img src="cid:072344a7")
+ # while the normal tag just refers to a name (e.g. img src="fred.jpg")
+ # The code below keeps the m-tag and discards the normal tag.
+ # If there are any m-tags like this, then the message also contains an
+ # attachment with a PR_CONTENT_ID property e.g. "072344a7". Actually,
+ # sometimes the m-tag is e.g. img src="http://outlook/welcome.html" and the
+ # attachment has a PR_CONTENT_LOCATION "http://outlook/welcome.html" instead
+ # of a PR_CONTENT_ID.
+ #
+ def rtf2html rtf
+ scan = StringScanner.new rtf
+ # require \fromhtml. is this worth keeping? apparently you see \\fromtext if it
+ # was converted from plain text.
+ return nil unless rtf["\\fromhtml"]
+ html = ''
+ ignore_tag = nil
+ # skip up to the first htmltag. return nil if we don't ever find one
+ return nil unless scan.scan_until /(?=\{\\\*\\htmltag)/
+ until scan.empty?
+ if scan.scan /\{/
+ elsif scan.scan /\}/
+ elsif scan.scan /\\\*\\htmltag(\d+) ?/
+ #p scan[1]
+ if ignore_tag == scan[1]
+ scan.scan_until /\}/
+ ignore_tag = nil
+ end
+ elsif scan.scan /\\\*\\mhtmltag(\d+) ?/
+ ignore_tag = scan[1]
+ elsif scan.scan /\\par ?/
+ html << "\r\n"
+ elsif scan.scan /\\tab ?/
+ html << "\t"
+ elsif scan.scan /\\'([0-9A-Za-z]{2})/
+ html << scan[1].hex.chr
+ elsif scan.scan /\\pntext/
+ scan.scan_until /\}/
+ elsif scan.scan /\\htmlrtf/
+ scan.scan_until /\\htmlrtf0 ?/
+ # a generic throw away unknown tags thing.
+ # the above 2 however, are handled specially
+ elsif scan.scan /\\[a-z-]+(\d+)? ?/
+ #elsif scan.scan /\\li(\d+) ?/
+ #elsif scan.scan /\\fi-(\d+) ?/
+ elsif scan.scan /[\r\n]/
+ elsif scan.scan /\\([{}\\])/
+ html << scan[1]
+ elsif scan.scan /(.)/
+ html << scan[1]
+ else
+ p :wtf
+ end
+ end
+ html.strip.empty? ? nil : html
+ end
+
+ module_function :rtf2html, :rtfdecompr
+ end
+end
+
diff --git a/vendor/ruby-msg/lib/mapi/types.rb b/vendor/ruby-msg/lib/mapi/types.rb
new file mode 100644
index 000000000..71416afd5
--- /dev/null
+++ b/vendor/ruby-msg/lib/mapi/types.rb
@@ -0,0 +1,51 @@
+require 'rubygems'
+require 'ole/types'
+
+module Mapi
+ Log = Logger.new_with_callstack
+
+ module Types
+ #
+ # Mapi property types, taken from http://msdn2.microsoft.com/en-us/library/bb147591.aspx.
+ #
+ # The fields are [mapi name, variant name, description]. Maybe I should just make it a
+ # struct.
+ #
+ # seen some synonyms here, like PT_I8 vs PT_LONG. seen stuff like PT_SRESTRICTION, not
+ # sure what that is. look at `grep ' PT_' data/mapitags.yaml | sort -u`
+ # also, it has stuff like PT_MV_BINARY, where _MV_ probably means multi value, and is
+ # likely just defined to | in 0x1000.
+ #
+ # Note that the last 2 are the only ones where the Mapi value differs from the Variant value
+ # for the corresponding variant type. Odd. Also, the last 2 are currently commented out here
+ # because of the clash.
+ #
+ # Note 2 - the strings here say VT_BSTR, but I don't have that defined in Ole::Types. Should
+ # maybe change them to match. I've also seen reference to PT_TSTRING, which is defined as some
+ # sort of get unicode first, and fallback to ansii or something.
+ #
+ DATA = {
+ 0x0001 => ['PT_NULL', 'VT_NULL', 'Null (no valid data)'],
+ 0x0002 => ['PT_SHORT', 'VT_I2', '2-byte integer (signed)'],
+ 0x0003 => ['PT_LONG', 'VT_I4', '4-byte integer (signed)'],
+ 0x0004 => ['PT_FLOAT', 'VT_R4', '4-byte real (floating point)'],
+ 0x0005 => ['PT_DOUBLE', 'VT_R8', '8-byte real (floating point)'],
+ 0x0006 => ['PT_CURRENCY', 'VT_CY', '8-byte integer (scaled by 10,000)'],
+ 0x000a => ['PT_ERROR', 'VT_ERROR', 'SCODE value; 32-bit unsigned integer'],
+ 0x000b => ['PT_BOOLEAN', 'VT_BOOL', 'Boolean'],
+ 0x000d => ['PT_OBJECT', 'VT_UNKNOWN', 'Data object'],
+ 0x001e => ['PT_STRING8', 'VT_BSTR', 'String'],
+ 0x001f => ['PT_UNICODE', 'VT_BSTR', 'String'],
+ 0x0040 => ['PT_SYSTIME', 'VT_DATE', '8-byte real (date in integer, time in fraction)'],
+ #0x0102 => ['PT_BINARY', 'VT_BLOB', 'Binary (unknown format)'],
+ #0x0102 => ['PT_CLSID', 'VT_CLSID', 'OLE GUID']
+ }
+
+ module Constants
+ DATA.each { |num, (mapi_name, variant_name, desc)| const_set mapi_name, num }
+ end
+
+ include Constants
+ end
+end
+
diff --git a/vendor/ruby-msg/lib/mime.rb b/vendor/ruby-msg/lib/mime.rb
new file mode 100644
index 000000000..4340e4901
--- /dev/null
+++ b/vendor/ruby-msg/lib/mime.rb
@@ -0,0 +1,165 @@
+#
+# = Introduction
+#
+# A *basic* mime class for _really_ _basic_ and probably non-standard parsing
+# and construction of MIME messages.
+#
+# Intended for two main purposes in this project:
+# 1. As the container that is used to build up the message for eventual
+# serialization as an eml.
+# 2. For assistance in parsing the +transport_message_headers+ provided in .msg files,
+# which are then kept through to the final eml.
+#
+# = TODO
+#
+# * Better streaming support, rather than an all-in-string approach.
+# * Add +OrderedHash+ optionally, to not lose ordering in headers.
+# * A fair bit remains to be done for this class, its fairly immature. But generally I'd like
+# to see it be more generally useful.
+# * All sorts of correctness issues, encoding particular.
+# * Duplication of work in net/http.rb's +HTTPHeader+? Don't know if the overlap is sufficient.
+# I don't want to lower case things, just for starters.
+# * Mime was the original place I wrote #to_tree, intended as a quick debug hack.
+#
+class SimpleMime
+ Hash = begin
+ require 'orderedhash'
+ OrderedHash
+ rescue LoadError
+ Hash
+ end
+
+ attr_reader :headers, :body, :parts, :content_type, :preamble, :epilogue
+
+ # Create a SimpleMime object using +str+ as an initial serialization, which must contain headers
+ # and a body (even if empty). Needs work.
+ def initialize str, ignore_body=false
+ headers, @body = $~[1..-1] if str[/(.*?\r?\n)(?:\r?\n(.*))?\Z/m]
+
+ @headers = Hash.new { |hash, key| hash[key] = [] }
+ @body ||= ''
+ headers.to_s.scan(/^\S+:\s*.*(?:\n\t.*)*/).each do |header|
+ @headers[header[/(\S+):/, 1]] << header[/\S+:\s*(.*)/m, 1].gsub(/\s+/m, ' ').strip # this is kind of wrong
+ end
+
+ # don't have to have content type i suppose
+ @content_type, attrs = nil, {}
+ if content_type = @headers['Content-Type'][0]
+ @content_type, attrs = SimpleMime.split_header content_type
+ end
+
+ return if ignore_body
+
+ if multipart?
+ if body.empty?
+ @preamble = ''
+ @epilogue = ''
+ @parts = []
+ else
+ # we need to split the message at the boundary
+ boundary = attrs['boundary'] or raise "no boundary for multipart message"
+
+ # splitting the body:
+ parts = body.split(/--#{Regexp.quote boundary}/m)
+ unless parts[-1] =~ /^--/; warn "bad multipart boundary (missing trailing --)"
+ else parts[-1][0..1] = ''
+ end
+ parts.each_with_index do |part, i|
+ part =~ /^(\r?\n)?(.*?)(\r?\n)?\Z/m
+ part.replace $2
+ warn "bad multipart boundary" if (1...parts.length-1) === i and !($1 && $3)
+ end
+ @preamble = parts.shift
+ @epilogue = parts.pop
+ @parts = parts.map { |part| SimpleMime.new part }
+ end
+ end
+ end
+
+ def multipart?
+ @content_type && @content_type =~ /^multipart/ ? true : false
+ end
+
+ def inspect
+ # add some extra here.
+ "#<SimpleMime content_type=#{@content_type.inspect}>"
+ end
+
+ def to_tree
+ if multipart?
+ str = "- #{inspect}\n"
+ parts.each_with_index do |part, i|
+ last = i == parts.length - 1
+ part.to_tree.split(/\n/).each_with_index do |line, j|
+ str << " #{last ? (j == 0 ? "\\" : ' ') : '|'}" + line + "\n"
+ end
+ end
+ str
+ else
+ "- #{inspect}\n"
+ end
+ end
+
+ def to_s opts={}
+ opts = {:boundary_counter => 0}.merge opts
+ if multipart?
+ boundary = SimpleMime.make_boundary opts[:boundary_counter] += 1, self
+ @body = [preamble, parts.map { |part| "\r\n" + part.to_s(opts) + "\r\n" }, "--\r\n" + epilogue].
+ flatten.join("\r\n--" + boundary)
+ content_type, attrs = SimpleMime.split_header @headers['Content-Type'][0]
+ attrs['boundary'] = boundary
+ @headers['Content-Type'] = [([content_type] + attrs.map { |key, val| %{#{key}="#{val}"} }).join('; ')]
+ end
+
+ str = ''
+ @headers.each do |key, vals|
+ vals.each { |val| str << "#{key}: #{val}\r\n" }
+ end
+ str << "\r\n" + @body
+ end
+
+ def self.split_header header
+ # FIXME: haven't read standard. not sure what its supposed to do with " in the name, or if other
+ # escapes are allowed. can't test on windows as " isn't allowed anyway. can be fixed with more
+ # accurate parser later.
+ # maybe move to some sort of Header class. but not all headers should be of it i suppose.
+ # at least add a join_header then, taking name and {}. for use in SimpleMime#to_s (for boundary
+ # rewrite), and Attachment#to_mime, among others...
+ attrs = {}
+ header.scan(/;\s*([^\s=]+)\s*=\s*("[^"]*"|[^\s;]*)\s*/m).each do |key, value|
+ if attrs[key]; warn "ignoring duplicate header attribute #{key.inspect}"
+ else attrs[key] = value[/^"/] ? value[1..-2] : value
+ end
+ end
+
+ [header[/^[^;]+/].strip, attrs]
+ end
+
+ # +i+ is some value that should be unique for all multipart boundaries for a given message
+ def self.make_boundary i, extra_obj = SimpleMime
+ "----_=_NextPart_#{'%03d' % i}_#{'%08x' % extra_obj.object_id}.#{'%08x' % Time.now}"
+ end
+end
+
+=begin
+things to consider for header work.
+encoded words:
+Subject: =?iso-8859-1?q?p=F6stal?=
+
+and other mime funkyness:
+Content-Disposition: attachment;
+ filename*0*=UTF-8''09%20%D7%90%D7%A5;
+ filename*1*=%20%D7%A1%D7%91-;
+ filename*2*=%D7%A7%95%A5.wma
+Content-Transfer-Encoding: base64
+
+and another, doing a test with an embedded newline in an attachment name, I
+get this output from evolution. I get the feeling that this is probably a bug
+with their implementation though, they weren't expecting new lines in filenames.
+Content-Disposition: attachment; filename="asdf'b\"c
+d efgh=i: ;\\j"
+d efgh=i: ;\\j"; charset=us-ascii
+Content-Type: text/plain; name="asdf'b\"c"; charset=us-ascii
+
+=end
+
diff --git a/vendor/ruby-msg/lib/orderedhash.rb b/vendor/ruby-msg/lib/orderedhash.rb
new file mode 100644
index 000000000..16a4f5860
--- /dev/null
+++ b/vendor/ruby-msg/lib/orderedhash.rb
@@ -0,0 +1,218 @@
+# = OrderedHash
+#
+# == Version
+# 1.2006.07.13 (change of the first number means Big Change)
+#
+# == Description
+# Hash which preserves order of added items (like PHP array).
+#
+# == Usage
+#
+# (see examples directory under the ruby gems root directory)
+#
+# require 'rubygems'
+# require 'ordered_hash'
+#
+# hsh = OrderedHash.new
+# hsh['z'] = 1
+# hsh['a'] = 2
+# hsh['c'] = 3
+# p hsh.keys # ['z','a','c']
+#
+# == Source
+# http://simplypowerful.1984.cz/goodlibs/1.2006.07.13
+#
+# == Author
+# jan molic (/mig/at_sign/1984/dot/cz/)
+#
+# == Thanks to
+# Andrew Johnson for his suggestions and fixes of Hash[], merge, to_a, inspect and shift
+# Desmond Dsouza for == fixes
+#
+# == Licence
+# You can redistribute it and/or modify it under the same terms of Ruby's license;
+# either the dual license version in 2003, or any later version.
+#
+
+class OrderedHash < Hash
+
+ attr_accessor :order
+
+ class << self
+
+ def [] *args
+ hsh = OrderedHash.new
+ if Hash === args[0]
+ hsh.replace args[0]
+ elsif (args.size % 2) != 0
+ raise ArgumentError, "odd number of elements for Hash"
+ else
+ hsh[args.shift] = args.shift while args.size > 0
+ end
+ hsh
+ end
+
+ end
+
+ def initialize(*a, &b)
+ super
+ @order = []
+ end
+
+ def store_only a,b
+ store a,b
+ end
+
+ alias orig_store store
+
+ def store a,b
+ @order.push a unless has_key? a
+ super a,b
+ end
+
+ alias []= store
+
+ def == hsh2
+ return hsh2==self if !hsh2.is_a?(OrderedHash)
+ return false if @order != hsh2.order
+ super hsh2
+ end
+
+ def clear
+ @order = []
+ super
+ end
+
+ def delete key
+ @order.delete key
+ super
+ end
+
+ def each_key
+ @order.each { |k| yield k }
+ self
+ end
+
+ def each_value
+ @order.each { |k| yield self[k] }
+ self
+ end
+
+ def each
+ @order.each { |k| yield k,self[k] }
+ self
+ end
+
+ alias each_pair each
+
+ def delete_if
+ @order.clone.each { |k|
+ delete k if yield
+ }
+ self
+ end
+
+ def values
+ ary = []
+ @order.each { |k| ary.push self[k] }
+ ary
+ end
+
+ def keys
+ @order
+ end
+
+ def invert
+ hsh2 = Hash.new
+ @order.each { |k| hsh2[self[k]] = k }
+ hsh2
+ end
+
+ def reject &block
+ self.dup.delete_if( &block )
+ end
+
+ def reject! &block
+ hsh2 = reject( &block )
+ self == hsh2 ? nil : hsh2
+ end
+
+ def replace hsh2
+ @order = hsh2.keys
+ super hsh2
+ end
+
+ def shift
+ key = @order.first
+ key ? [key,delete(key)] : super
+ end
+
+ def unshift k,v
+ unless self.include? k
+ @order.unshift k
+ orig_store(k,v)
+ true
+ else
+ false
+ end
+ end
+
+ def push k,v
+ unless self.include? k
+ @order.push k
+ orig_store(k,v)
+ true
+ else
+ false
+ end
+ end
+
+ def pop
+ key = @order.last
+ key ? [key,delete(key)] : nil
+ end
+
+ def first
+ self[@order.first]
+ end
+
+ def last
+ self[@order.last]
+ end
+
+ def to_a
+ ary = []
+ each { |k,v| ary << [k,v] }
+ ary
+ end
+
+ def to_s
+ self.to_a.to_s
+ end
+
+ def inspect
+ ary = []
+ each {|k,v| ary << k.inspect + "=>" + v.inspect}
+ '{' + ary.join(", ") + '}'
+ end
+
+ def update hsh2
+ hsh2.each { |k,v| self[k] = v }
+ self
+ end
+
+ alias :merge! update
+
+ def merge hsh2
+ self.dup update(hsh2)
+ end
+
+ def select
+ ary = []
+ each { |k,v| ary << [k,v] if yield k,v }
+ ary
+ end
+
+end
+
+#=end
diff --git a/vendor/ruby-msg/lib/rtf.rb b/vendor/ruby-msg/lib/rtf.rb
new file mode 100755
index 000000000..3afac68a8
--- /dev/null
+++ b/vendor/ruby-msg/lib/rtf.rb
@@ -0,0 +1,109 @@
+require 'stringio'
+
+# this file is pretty crap, its just to ensure there is always something readable if
+# there is an rtf only body, with no html encapsulation.
+
+module RTF
+ class Tokenizer
+ def self.process io
+ while true do
+ case c = io.getc
+ when ?{; yield :open_group
+ when ?}; yield :close_group
+ when ?\\
+ case c = io.getc
+ when ?{, ?}, ?\\; yield :text, c.chr
+ when ?'; yield :text, [io.read(2)].pack('H*')
+ when ?a..?z, ?A..?Z
+ # read control word
+ str = c.chr
+ str << c while c = io.read(1) and c =~ /[a-zA-Z]/
+ neg = 1
+ neg = -1 and c = io.read(1) if c == '-'
+ num = if c =~ /[0-9]/
+ num = c
+ num << c while c = io.read(1) and c =~ /[0-9]/
+ num.to_i * neg
+ end
+ raise "invalid rtf stream" if neg == -1 and !num # ???? \blahblah- some text
+ io.seek(-1, IO::SEEK_CUR) if c != ' '
+ yield :control_word, str, num
+ when nil
+ raise "invalid rtf stream" # \EOF
+ else
+ # other kind of control symbol
+ yield :control_symbol, c.chr
+ end
+ when nil
+ return
+ when ?\r, ?\n
+ # ignore
+ else yield :text, c.chr
+ end
+ end
+ end
+ end
+
+ class Converter
+ # crappy
+ def self.rtf2text str, format=:text
+ group = 0
+ text = ''
+ text << "<html>\n<body>" if format == :html
+ group_type = []
+ group_tags = []
+ RTF::Tokenizer.process(StringIO.new(str)) do |a, b, c|
+ add_text = ''
+ case a
+ when :open_group; group += 1; group_type[group] = nil; group_tags[group] = []
+ when :close_group; group_tags[group].reverse.each { |t| text << "</#{t}>" }; group -= 1;
+ when :control_word; # ignore
+ group_type[group] ||= b
+ # maybe change this to use utf8 where possible
+ add_text = if b == 'par' || b == 'line' || b == 'page'; "\n"
+ elsif b == 'tab' || b == 'cell'; "\t"
+ elsif b == 'endash' || b == 'emdash'; "-"
+ elsif b == 'emspace' || b == 'enspace' || b == 'qmspace'; " "
+ elsif b == 'ldblquote'; '"'
+ else ''
+ end
+ if b == 'b' || b == 'i' and format == :html
+ close = c == 0 ? '/' : ''
+ text << "<#{close}#{b}>"
+ if c == 0
+ group_tags[group].delete b
+ else
+ group_tags[group] << b
+ end
+ end
+ # lot of other ones belong in here.\
+=begin
+\bullet Bullet character.
+\lquote Left single quotation mark.
+\rquote Right single quotation mark.
+\ldblquote Left double quotation mark.
+\rdblquote
+=end
+ when :control_symbol; # ignore
+ group_type[group] ||= b
+ add_text = ' ' if b == '~' # non-breakable space
+ add_text = '-' if b == '_' # non-breakable hypen
+ when :text
+ add_text = b if group <= 1 or group_type[group] == 'rtlch' && !group_type[0...group].include?('*')
+ end
+ if format == :html
+ text << add_text.gsub(/([<>&"'])/) do
+ ent = { '<' => 'lt', '>' => 'gt', '&' => 'amp', '"' => 'quot', "'" => 'apos' }[$1]
+ "&#{ent};"
+ end
+ text << '<br>' if add_text == "\n"
+ else
+ text << add_text
+ end
+ end
+ text << "</body>\n</html>\n" if format == :html
+ text
+ end
+ end
+end
+
diff --git a/vendor/ruby-ole/ChangeLog b/vendor/ruby-ole/ChangeLog
new file mode 100644
index 000000000..1e7c80b59
--- /dev/null
+++ b/vendor/ruby-ole/ChangeLog
@@ -0,0 +1,62 @@
+== 1.2.8.2 / 2009-01-01
+
+- Update code to support ruby 1.9.1
+
+== 1.2.8.1 / 2008-10-22
+
+- Fix a couple of breakages when using $KCODE = 'UTF8'
+
+== 1.2.8 / 2008-10-08
+
+- Check in the new fixes to the mbat support.
+- Update README to be a bit more useful.
+
+== 1.2.7 / 2008-08-12
+
+- Prepare Ole::Types::PropertySet for write support.
+- Introduce Ole::Storage#meta_data as an easy interface to meta data stored
+ within various property sets.
+- Add new --metadata action to oletool to dump said metadata.
+- Add new --mimetype action to oletool, and corresponding Ole::Storage#mime_type
+ function to try to guess mime type of a file based on some simple heuristics.
+- Restructure project files a bit, and pull in file_system & meta_data support
+ by default.
+- More tests - now have 100% coverage.
+
+== 1.2.6 / 2008-07-21
+
+- Fix FileClass#expand_path to work properly on darwin (issue #2)
+- Guard against Enumerable#sum clash with active support (issue #3)
+
+== 1.2.5 / 2008-02-16
+
+- Make all tests pass on ruby 1.9.
+
+== 1.2.4 / 2008-01-09
+
+- Make all tests pass on windows (issue #1).
+- Make all tests pass on a power pc (running ubuntu).
+- Property set convenience access functions.
+
+== 1.2.3 / 2007-12-28
+
+- MBAT write support re-implmented. Can now write files over ~8mb again.
+- Minor fixes (truncation in #flush, file modification timestamps)
+- More test coverage
+- Initial (read-only) property set support.
+- Complete filesystem api, to pass most of the rubyzip tests.
+- Add a ChangeLog :).
+
+== 1.2.2 / 2007-11-05
+
+- Lots of test updates, 90% coverage.
+- Fix +to_tree+ method to be more efficient, and stream output.
+- Optimizations from benchmarks and profiling, mostly for writes. Fixed
+ AllocationTable#resize_chain, RangesIOResizable#truncate and
+ AllocationTable#free_block.
+- Add in filesystem test file from rubyzip, and start working on a
+ filesystem api.
+
+== 1.2.1 / 2007-08-20
+
+- Separate out from ruby-msg as new project.
diff --git a/vendor/ruby-ole/README b/vendor/ruby-ole/README
new file mode 100644
index 000000000..0208c5abd
--- /dev/null
+++ b/vendor/ruby-ole/README
@@ -0,0 +1,115 @@
+= Introduction
+
+The ruby-ole library provides a variety of functions primarily for
+working with OLE2 structured storage files, such as those produced by
+Microsoft Office - eg *.doc, *.msg etc.
+
+= Example Usage
+
+Here are some examples of how to use the library functionality,
+categorised roughly by purpose.
+
+1. Reading and writing files within an OLE container
+
+ The recommended way to manipulate the contents is via the
+ "file_system" API, whereby you use Ole::Storage instance methods
+ similar to the regular File and Dir class methods.
+
+ ole = Ole::Storage.open('oleWithDirs.ole', 'rb+')
+ p ole.dir.entries('.') # => [".", "..", "dir1", "dir2", "file1"]
+ p ole.file.read('file1')[0, 25] # => "this is the entry 'file1'"
+ ole.dir.mkdir('newdir')
+
+2. Accessing OLE meta data
+
+ Some convenience functions are provided for (currently read only)
+ access to OLE property sets and other sources of meta data.
+
+ ole = Ole::Storage.open('test_word_95.doc')
+ p ole.meta_data.file_format # => "MSWordDoc"
+ p ole.meta_data.mime_type # => "application/msword"
+ p ole.meta_data.doc_author.split.first # => "Charles"
+
+3. Raw access to underlying OLE internals
+
+ This is probably of little interest to most developers using the
+ library, but for some use cases you may need to drop down to the
+ lower level API on which the "file_system" API is constructed,
+ which exposes more of the format details.
+
+ <tt>Ole::Storage</tt> files can have multiple files with the same name,
+ or with a slash in the name, and other things that are probably
+ strictly invalid. This API is the only way to access those files.
+
+ You can access the header object directly:
+
+ p ole.header.num_sbat # => 1
+ p ole.header.magic.unpack('H*') # => ["d0cf11e0a1b11ae1"]
+
+ You can directly access the array of all Dirent objects,
+ including the root:
+
+ p ole.dirents.length # => 5
+ puts ole.root.to_tree
+ # =>
+ - #<Dirent:"Root Entry">
+ |- #<Dirent:"\001Ole" size=20 data="\001\000\000\002\000...">
+ |- #<Dirent:"\001CompObj" size=98 data="\001\000\376\377\003...">
+ |- #<Dirent:"WordDocument" size=2574 data="\334\245e\000-...">
+ \- #<Dirent:"\005SummaryInformation" size=54788 data="\376\377\000\000\001...">
+
+ You can access (through RangesIO methods, or by using the
+ relevant Dirent and AllocationTable methods) information like where within
+ the container a stream is located (these are offset/length pairs):
+
+ p ole.root["\001CompObj"].open { |io| io.ranges } # => [[0, 64], [64, 34]]
+
+See the documentation for each class for more details.
+
+= Thanks
+
+* The code contained in this project was initially based on chicago's libole
+ (source available at http://prdownloads.sf.net/chicago/ole.tgz).
+
+* It was later augmented with some corrections by inspecting pole, and (purely
+ for header definitions) gsf.
+
+* The property set parsing code came from the apache java project POIFS.
+
+* The excellent idea for using a pseudo file system style interface by providing
+ #file and #dir methods which mimic File and Dir, was borrowed (along with almost
+ unchanged tests!) from Thomas Sondergaard's rubyzip.
+
+= TODO
+
+== 1.2.9
+
+* add buffering to rangesio so that performance for small reads and writes
+ isn't so awful. maybe try and remove the bottlenecks of unbuffered first
+ with more profiling, then implement the buffering on top of that.
+* fix mode strings - like truncate when using 'w+', supporting append
+ 'a+' modes etc. done?
+* make ranges io obey readable vs writeable modes.
+* more RangesIO completion. ie, doesn't support #<< at the moment.
+* maybe some oletool doc.
+* make sure `rake test' runs tests both with $KCODE='UTF8', and without,
+ and maybe ensure i don't regress on 1.9 and jruby either now that they're
+ fixed.
+
+== 1.3.1
+
+* fix property sets a bit more. see TODO in Ole::Storage::MetaData
+* ability to zero out padding and unused blocks
+* case insensitive mode for ole/file_system?
+* better tests for mbat support.
+* further doc cleanup
+* add in place testing for jruby and ruby1.9
+
+== Longer term
+
+* more benchmarking, profiling, and speed fixes. was thinking vs other
+ ruby filesystems (eg, vs File/Dir itself, and vs rubyzip), and vs other
+ ole implementations (maybe perl's, and poifs) just to check its in the
+ ballpark, with no remaining silly bottlenecks.
+* supposedly vba does something weird to ole files. test that.
+
diff --git a/vendor/ruby-ole/Rakefile b/vendor/ruby-ole/Rakefile
new file mode 100644
index 000000000..1153bb39a
--- /dev/null
+++ b/vendor/ruby-ole/Rakefile
@@ -0,0 +1,209 @@
+require 'rake/rdoctask'
+require 'rake/testtask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+
+require 'rbconfig'
+require 'fileutils'
+
+$:.unshift 'lib'
+
+require 'ole/storage'
+
+PKG_NAME = 'ruby-ole'
+PKG_VERSION = Ole::Storage::VERSION
+
+task :default => [:test]
+
+Rake::TestTask.new do |t|
+ t.test_files = FileList["test/test_*.rb"]
+ t.warning = true
+ t.verbose = true
+end
+
+begin
+ require 'rcov/rcovtask'
+ # NOTE: this will not do anything until you add some tests
+ desc "Create a cross-referenced code coverage report"
+ Rcov::RcovTask.new do |t|
+ t.test_files = FileList['test/test*.rb']
+ t.ruby_opts << "-Ilib" # in order to use this rcov
+ t.rcov_opts << "--xrefs" # comment to disable cross-references
+ t.verbose = true
+ end
+rescue LoadError
+ # Rcov not available
+end
+
+Rake::RDocTask.new do |t|
+ t.rdoc_dir = 'doc'
+ t.rdoc_files.include 'lib/**/*.rb'
+ t.rdoc_files.include 'README', 'ChangeLog'
+ t.title = "#{PKG_NAME} documentation"
+ t.options += %w[--line-numbers --inline-source --tab-width 2]
+ t.main = 'README'
+end
+
+spec = Gem::Specification.new do |s|
+ s.name = PKG_NAME
+ s.version = PKG_VERSION
+ s.summary = %q{Ruby OLE library.}
+ s.description = %q{A library for easy read/write access to OLE compound documents for Ruby.}
+ s.authors = ['Charles Lowe']
+ s.email = %q{aquasync@gmail.com}
+ s.homepage = %q{http://code.google.com/p/ruby-ole}
+ s.rubyforge_project = %q{ruby-ole}
+
+ s.executables = ['oletool']
+ s.files = ['README', 'Rakefile', 'ChangeLog', 'data/propids.yaml']
+ s.files += FileList['lib/**/*.rb']
+ s.files += FileList['test/test_*.rb', 'test/*.doc']
+ s.files += FileList['test/oleWithDirs.ole', 'test/test_SummaryInformation']
+ s.files += FileList['bin/*']
+ s.test_files = FileList['test/test_*.rb']
+
+ s.has_rdoc = true
+ s.extra_rdoc_files = ['README', 'ChangeLog']
+ s.rdoc_options += [
+ '--main', 'README',
+ '--title', "#{PKG_NAME} documentation",
+ '--tab-width', '2'
+ ]
+end
+
+Rake::GemPackageTask.new(spec) do |t|
+ t.gem_spec = spec
+ t.need_tar = true
+ t.need_zip = false
+ t.package_dir = 'build'
+end
+
+desc 'Run various benchmarks'
+task :benchmark do
+ require 'benchmark'
+ require 'tempfile'
+ require 'ole/file_system'
+
+ # should probably add some read benchmarks too
+ def write_benchmark opts={}
+ files, size = opts[:files], opts[:size]
+ block_size = opts[:block_size] || 100_000
+ block = 0.chr * block_size
+ blocks, remaining = size.divmod block_size
+ remaining = 0.chr * remaining
+ Tempfile.open 'ole_storage_benchmark' do |temp|
+ Ole::Storage.open temp do |ole|
+ files.times do |i|
+ ole.file.open "file_#{i}", 'w' do |f|
+ blocks.times { f.write block }
+ f.write remaining
+ end
+ end
+ end
+ end
+ end
+
+ Benchmark.bm do |bm|
+ bm.report 'write_1mb_1x5' do
+ 5.times { write_benchmark :files => 1, :size => 1_000_000 }
+ end
+
+ bm.report 'write_1mb_2x5' do
+ 5.times { write_benchmark :files => 1_000, :size => 1_000 }
+ end
+ end
+end
+
+=begin
+
+1.2.1:
+
+ user system total real
+write_1mb_1x5 73.920000 8.400000 82.320000 ( 91.893138)
+
+revision 17 (speed up AllocationTable#free_block by using
+@sparse attribute, and using Array#index otherwise):
+
+ user system total real
+write_1mb_1x5 57.910000 6.190000 64.100000 ( 66.207993)
+write_1mb_2x5266.310000 31.750000 298.060000 (305.877203)
+
+add in extra resize_chain fix (return blocks to avoid calling
+AllocationTable#chain twice):
+
+ user system total real
+write_1mb_1x5 43.140000 5.480000 48.620000 ( 51.835942)
+
+add in RangesIOResizeable fix (cache @blocks, to avoid calling
+AllocationTable#chain at all when resizing now, just pass it
+to AllocationTable#resize_chain):
+
+ user system total real
+write_1mb_1x5 29.770000 5.180000 34.950000 ( 39.916747)
+
+40 seconds is still a really long time to write out 5 megs.
+of course, this is all with a 1_000 byte block size, which is
+a very small wite. upping this to 100_000 bytes:
+
+ user system total real
+write_1mb_1x5 0.540000 0.130000 0.670000 ( 1.051862)
+
+so it seems that that makes a massive difference. so i really
+need buffering in RangesIO if I don't want it to really hurt
+for small writes, as all the resize code is kind of expensive.
+
+one of the costly things at the moment, is RangesIO#offset_and_size,
+which is called for each write, and re-finds which range we are in.
+that should obviously be changed, to a fixed one that is invalidated
+on seeks. buffering would hide that problem to some extent, but i
+should fix it anyway.
+
+re-running the original 1.2.1 with 100_000 byte block size:
+
+ user system total real
+write_1mb_1x5 15.590000 2.230000 17.820000 ( 18.704910)
+
+so there the really badly non-linear AllocationTable#resize_chain is
+being felt.
+
+back to current working copy, running full benchmark:
+
+ user system total real
+write_1mb_1x5 0.530000 0.150000 0.680000 ( 0.708919)
+write_1mb_2x5227.940000 31.260000 259.200000 (270.200960)
+
+not surprisingly, the second case hasn't been helped much by the fixes
+so far, as they only really help multiple resizes and writes for a file.
+this could be pain in the new file system code - potentially searching
+through Dirent#children at creation time.
+
+to test, i'll profile creating 1_000 files, without writing anything:
+
+ user system total real
+write_1mb_2x5 16.990000 1.830000 18.820000 ( 19.900568)
+
+hmmm, so thats not all of it. maybe its the initial chain calls, etc?
+writing 1 byte:
+
+ user system total real
+write_1mb_1x5 0.520000 0.120000 0.640000 ( 0.660638)
+write_1mb_2x5 19.810000 2.280000 22.090000 ( 22.696214)
+
+weird.
+
+100 bytes:
+
+ user system total real
+write_1mb_1x5 0.560000 0.140000 0.700000 ( 1.424974)
+write_1mb_2x5 22.940000 2.840000 25.780000 ( 26.556346)
+
+500 bytes:
+
+ user system total real
+write_1mb_1x5 0.530000 0.150000 0.680000 ( 1.139738)
+write_1mb_2x5 77.260000 10.130000 87.390000 ( 91.671086)
+
+what happens there? very strange.
+
+=end
+
diff --git a/vendor/ruby-ole/bin/oletool b/vendor/ruby-ole/bin/oletool
new file mode 100755
index 000000000..d81afab5a
--- /dev/null
+++ b/vendor/ruby-ole/bin/oletool
@@ -0,0 +1,41 @@
+#! /usr/bin/ruby
+
+require 'optparse'
+require 'rubygems'
+require 'ole/storage'
+
+def oletool
+ opts = {:verbose => false, :action => :tree}
+ op = OptionParser.new do |op|
+ op.banner = "Usage: oletool [options] [files]"
+ op.separator ''
+ op.on('-t', '--tree', 'Dump ole trees for files (default)') { opts[:action] = :tree }
+ op.on('-r', '--repack', 'Repack the ole files in canonical form') { opts[:action] = :repack }
+ op.on('-m', '--mimetype', 'Print the guessed mime types') { opts[:action] = :mimetype }
+ op.on('-y', '--metadata', 'Dump the internal meta data as YAML') { opts[:action] = :metadata }
+ op.separator ''
+ op.on('-v', '--[no-]verbose', 'Run verbosely') { |v| opts[:verbose] = v }
+ op.on_tail('-h', '--help', 'Show this message') { puts op; exit }
+ end
+ files = op.parse ARGV
+ if files.empty?
+ puts 'Must specify 1 or more msg files.'
+ puts op
+ exit 1
+ end
+ Ole::Log.level = opts[:verbose] ? Logger::WARN : Logger::FATAL
+ files.each do |file|
+ case opts[:action]
+ when :tree
+ Ole::Storage.open(file) { |ole| puts ole.root.to_tree }
+ when :repack
+ Ole::Storage.open file, 'rb+', &:repack
+ when :metadata
+ Ole::Storage.open(file) { |ole| y ole.meta_data.to_h }
+ when :mimetype
+ puts Ole::Storage.open(file) { |ole| ole.meta_data.mime_type }
+ end
+ end
+end
+
+oletool
diff --git a/vendor/ruby-ole/data/propids.yaml b/vendor/ruby-ole/data/propids.yaml
new file mode 100644
index 000000000..9ac43ffe1
--- /dev/null
+++ b/vendor/ruby-ole/data/propids.yaml
@@ -0,0 +1,56 @@
+"{f29f85e0-4ff9-1068-ab91-08002b27b3d9}":
+ - FMTID_SummaryInformation
+ - 2: doc_title
+ 3: doc_subject
+ 4: doc_author
+ 5: doc_keywords
+ 6: doc_comments
+ 7: doc_template
+ 8: doc_last_author
+ 9: doc_rev_number
+ 10: doc_edit_time
+ 11: doc_last_printed
+ 12: doc_created_time
+ 13: doc_last_saved_time
+ 14: doc_page_count
+ 15: doc_word_count
+ 16: doc_char_count
+ 18: doc_app_name
+ 19: security
+
+"{d5cdd502-2e9c-101b-9397-08002b2cf9ae}":
+ - FMTID_DocSummaryInfo
+ - 2: doc_category
+ 3: doc_presentation_target
+ 4: doc_byte_count
+ 5: doc_line_count
+ 6: doc_para_count
+ 7: doc_slide_count
+ 8: doc_note_count
+ 9: doc_hidden_count
+ 10: mmclips
+ 11: scale_crop
+ 12: heading_pairs
+ 13: doc_part_titles
+ 14: doc_manager
+ 15: doc_company
+ 16: links_up_to_date
+
+"{d5cdd505-2e9c-101b-9397-08002b2cf9ae}":
+ - FMTID_UserDefinedProperties
+ - {}
+
+# just dumped these all here. if i can confirm any of these
+# better, i can update this file so they're recognized.
+#0b63e343-9ccc-11d0-bcdb-00805fccce04
+#0b63e350-9ccc-11d0-bcdb-00805fccce04 NetLibrary propset?
+#31f400a0-fd07-11cf-b9bd-00aa003db18e ScriptInfo propset?
+#49691c90-7e17-101a-a91c-08002b2ecda9 Query propset?
+#560c36c0-503a-11cf-baa1-00004c752a9a
+#70eb7a10-55d9-11cf-b75b-00aa0051fe20 HTMLInfo propset
+#85ac0960-1819-11d1-896f-00805f053bab message propset?
+#aa568eec-e0e5-11cf-8fda-00aa00a14f93 NNTP SummaryInformation propset?
+#b725f130-47ef-101a-a5f1-02608c9eebac Storage propset
+#c82bf596-b831-11d0-b733-00aa00a1ebd2 NetLibraryInfo propset
+#c82bf597-b831-11d0-b733-00aa00a1ebd2 LinkInformation propset?
+#d1b5d3f0-c0b3-11cf-9a92-00a0c908dbf1 LinkInformation propset?
diff --git a/vendor/ruby-ole/lib/ole/base.rb b/vendor/ruby-ole/lib/ole/base.rb
new file mode 100644
index 000000000..ee1bc0431
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/base.rb
@@ -0,0 +1,7 @@
+
+require 'ole/support'
+
+module Ole # :nodoc:
+ Log = Logger.new_with_callstack
+end
+
diff --git a/vendor/ruby-ole/lib/ole/file_system.rb b/vendor/ruby-ole/lib/ole/file_system.rb
new file mode 100644
index 000000000..24d330a92
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/file_system.rb
@@ -0,0 +1,2 @@
+# keeping this file around for now, but will delete later on...
+require 'ole/storage/file_system'
diff --git a/vendor/ruby-ole/lib/ole/ranges_io.rb b/vendor/ruby-ole/lib/ole/ranges_io.rb
new file mode 100644
index 000000000..bfca4fe09
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/ranges_io.rb
@@ -0,0 +1,231 @@
+# need IO::Mode
+require 'ole/support'
+
+#
+# = Introduction
+#
+# +RangesIO+ is a basic class for wrapping another IO object allowing you to arbitrarily reorder
+# slices of the input file by providing a list of ranges. Intended as an initial measure to curb
+# inefficiencies in the Dirent#data method just reading all of a file's data in one hit, with
+# no method to stream it.
+#
+# This class will encapuslate the ranges (corresponding to big or small blocks) of any ole file
+# and thus allow reading/writing directly to the source bytes, in a streamed fashion (so just
+# getting 16 bytes doesn't read the whole thing).
+#
+# In the simplest case it can be used with a single range to provide a limited io to a section of
+# a file.
+#
+# = Limitations
+#
+# * No buffering. by design at the moment. Intended for large reads
+#
+# = TODO
+#
+# On further reflection, this class is something of a joining/optimization of
+# two separate IO classes. a SubfileIO, for providing access to a range within
+# a File as a separate IO object, and a ConcatIO, allowing the presentation of
+# a bunch of io objects as a single unified whole.
+#
+# I will need such a ConcatIO if I'm to provide Mime#to_io, a method that will
+# convert a whole mime message into an IO stream, that can be read from.
+# It will just be the concatenation of a series of IO objects, corresponding to
+# headers and boundaries, as StringIO's, and SubfileIO objects, coming from the
+# original message proper, or RangesIO as provided by the Attachment#data, that
+# will then get wrapped by Mime in a Base64IO or similar, to get encoded on-the-
+# fly. Thus the attachment, in its plain or encoded form, and the message as a
+# whole never exists as a single string in memory, as it does now. This is a
+# fair bit of work to achieve, but generally useful I believe.
+#
+# This class isn't ole specific, maybe move it to my general ruby stream project.
+#
+class RangesIO
+ attr_reader :io, :mode, :ranges, :size, :pos
+ # +io+:: the parent io object that we are wrapping.
+ # +mode+:: the mode to use
+ # +params+:: hash of params.
+ # * :ranges - byte offsets, either:
+ # 1. an array of ranges [1..2, 4..5, 6..8] or
+ # 2. an array of arrays, where the second is length [[1, 1], [4, 1], [6, 2]] for the above
+ # (think the way String indexing works)
+ # * :close_parent - boolean to close parent when this object is closed
+ #
+ # NOTE: the +ranges+ can overlap.
+ def initialize io, mode='r', params={}
+ mode, params = 'r', mode if Hash === mode
+ ranges = params[:ranges]
+ @params = {:close_parent => false}.merge params
+ @mode = IO::Mode.new mode
+ @io = io
+ # convert ranges to arrays. check for negative ranges?
+ ranges ||= [0, io.size]
+ @ranges = ranges.map { |r| Range === r ? [r.begin, r.end - r.begin] : r }
+ # calculate size
+ @size = @ranges.inject(0) { |total, (pos, len)| total + len }
+ # initial position in the file
+ @pos = 0
+
+ # handle some mode flags
+ truncate 0 if @mode.truncate?
+ seek size if @mode.append?
+ end
+
+#IOError: closed stream
+# get this for reading, writing, everything...
+#IOError: not opened for writing
+
+ # add block form. TODO add test for this
+ def self.open(*args, &block)
+ ranges_io = new(*args)
+ if block_given?
+ begin; yield ranges_io
+ ensure; ranges_io.close
+ end
+ else
+ ranges_io
+ end
+ end
+
+ def pos= pos, whence=IO::SEEK_SET
+ case whence
+ when IO::SEEK_SET
+ when IO::SEEK_CUR
+ pos += @pos
+ when IO::SEEK_END
+ pos = @size + pos
+ else raise Errno::EINVAL
+ end
+ raise Errno::EINVAL unless (0...@size) === pos
+ @pos = pos
+ end
+
+ alias seek :pos=
+ alias tell :pos
+
+ def close
+ @io.close if @params[:close_parent]
+ end
+
+ # returns the [+offset+, +size+], pair inorder to read/write at +pos+
+ # (like a partial range), and its index.
+ def offset_and_size pos
+ total = 0
+ ranges.each_with_index do |(offset, size), i|
+ if pos <= total + size
+ diff = pos - total
+ return [offset + diff, size - diff], i
+ end
+ total += size
+ end
+ # should be impossible for any valid pos, (0...size) === pos
+ raise ArgumentError, "no range for pos #{pos.inspect}"
+ end
+
+ def eof?
+ @pos == @size
+ end
+
+ # read bytes from file, to a maximum of +limit+, or all available if unspecified.
+ def read limit=nil
+ data = ''
+ return data if eof?
+ limit ||= size
+ partial_range, i = offset_and_size @pos
+ # this may be conceptually nice (create sub-range starting where we are), but
+ # for a large range array its pretty wasteful. even the previous way was. but
+ # i'm not trying to optimize this atm. it may even go to c later if necessary.
+ ([partial_range] + ranges[i+1..-1]).each do |pos, len|
+ @io.seek pos
+ if limit < len
+ # convoluted, to handle read errors. s may be nil
+ s = @io.read limit
+ @pos += s.length if s
+ break data << s
+ end
+ # convoluted, to handle ranges beyond the size of the file
+ s = @io.read len
+ @pos += s.length if s
+ data << s
+ break if s.length != len
+ limit -= len
+ end
+ data
+ end
+
+ # you may override this call to update @ranges and @size, if applicable.
+ def truncate size
+ raise NotImplementedError, 'truncate not supported'
+ end
+
+ # using explicit forward instead of an alias now for overriding.
+ # should override truncate.
+ def size= size
+ truncate size
+ end
+
+ def write data
+ # short cut. needed because truncate 0 may return no ranges, instead of empty range,
+ # thus offset_and_size fails.
+ return 0 if data.empty?
+ data_pos = 0
+ # if we don't have room, we can use the truncate hook to make more space.
+ if data.length > @size - @pos
+ begin
+ truncate @pos + data.length
+ rescue NotImplementedError
+ raise IOError, "unable to grow #{inspect} to write #{data.length} bytes"
+ end
+ end
+ partial_range, i = offset_and_size @pos
+ ([partial_range] + ranges[i+1..-1]).each do |pos, len|
+ @io.seek pos
+ if data_pos + len > data.length
+ chunk = data[data_pos..-1]
+ @io.write chunk
+ @pos += chunk.length
+ data_pos = data.length
+ break
+ end
+ @io.write data[data_pos, len]
+ @pos += len
+ data_pos += len
+ end
+ data_pos
+ end
+
+ alias << write
+
+ # i can wrap it in a buffered io stream that
+ # provides gets, and appropriately handle pos,
+ # truncate. mostly added just to past the tests.
+ # FIXME
+ def gets
+ s = read 1024
+ i = s.index "\n"
+ @pos -= s.length - (i+1)
+ s[0..i]
+ end
+ alias readline :gets
+
+ def inspect
+ # the rescue is for empty files
+ pos, len = (@ranges[offset_and_size(@pos).last] rescue [nil, nil])
+ range_str = pos ? "#{pos}..#{pos+len}" : 'nil'
+ "#<#{self.class} io=#{io.inspect}, size=#@size, pos=#@pos, "\
+ "range=#{range_str}>"
+ end
+end
+
+# this subclass of ranges io explicitly ignores the truncate part of 'w' modes.
+# only really needed for the allocation table writes etc. maybe just use explicit modes
+# for those
+# better yet write a test that breaks before I fix it. added nodoc for the
+# time being.
+class RangesIONonResizeable < RangesIO # :nodoc:
+ def initialize io, mode='r', params={}
+ mode, params = 'r', mode if Hash === mode
+ flags = IO::Mode.new(mode).flags & ~IO::TRUNC
+ super io, flags, params
+ end
+end
+
diff --git a/vendor/ruby-ole/lib/ole/storage.rb b/vendor/ruby-ole/lib/ole/storage.rb
new file mode 100644
index 000000000..02e851df7
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/storage.rb
@@ -0,0 +1,3 @@
+require 'ole/storage/base'
+require 'ole/storage/file_system'
+require 'ole/storage/meta_data'
diff --git a/vendor/ruby-ole/lib/ole/storage/base.rb b/vendor/ruby-ole/lib/ole/storage/base.rb
new file mode 100755
index 000000000..3c41b21a2
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/storage/base.rb
@@ -0,0 +1,916 @@
+require 'tempfile'
+
+require 'ole/base'
+require 'ole/types'
+require 'ole/ranges_io'
+
+module Ole # :nodoc:
+ #
+ # This class is the primary way the user interacts with an OLE storage file.
+ #
+ # = TODO
+ #
+ # * the custom header cruft for Header and Dirent needs some love.
+ # * i have a number of classes doing load/save combos: Header, AllocationTable, Dirent,
+ # and, in a manner of speaking, but arguably different, Storage itself.
+ # they have differing api's which would be nice to rethink.
+ # AllocationTable::Big must be created aot now, as it is used for all subsequent reads.
+ #
+ class Storage
+ # thrown for any bogus OLE file errors.
+ class FormatError < StandardError # :nodoc:
+ end
+
+ VERSION = '1.2.8.2'
+
+ # options used at creation time
+ attr_reader :params
+ # The top of the ole tree structure
+ attr_reader :root
+ # The tree structure in its original flattened form. only valid after #load, or #flush.
+ attr_reader :dirents
+ # The underlying io object to/from which the ole object is serialized, whether we
+ # should close it, and whether it is writeable
+ attr_reader :io, :close_parent, :writeable
+ # Low level internals, you probably shouldn't need to mess with these
+ attr_reader :header, :bbat, :sbat, :sb_file
+
+ # +arg+ should be either a filename, or an +IO+ object, and needs to be seekable.
+ # +mode+ is optional, and should be a regular mode string.
+ def initialize arg, mode=nil, params={}
+ params, mode = mode, nil if Hash === mode
+ params = {:update_timestamps => true}.merge(params)
+ @params = params
+
+ # get the io object
+ @close_parent, @io = if String === arg
+ mode ||= 'rb'
+ [true, open(arg, mode)]
+ else
+ raise ArgumentError, 'unable to specify mode string with io object' if mode
+ [false, arg]
+ end
+ # do we have this file opened for writing? don't know of a better way to tell
+ # (unless we parse the mode string in the open case)
+ # hmmm, note that in ruby 1.9 this doesn't work anymore. which is all the more
+ # reason to use mode string parsing when available, and fall back to something like
+ # io.writeable? otherwise.
+ @writeable = begin
+ if mode
+ IO::Mode.new(mode).writeable?
+ else
+ @io.flush
+ # this is for the benefit of ruby-1.9
+ @io.syswrite('') if @io.respond_to?(:syswrite)
+ true
+ end
+ rescue IOError
+ false
+ end
+ # silence undefined warning in clear
+ @sb_file = nil
+ # if the io object has data, we should load it, otherwise start afresh
+ # this should be based on the mode string rather.
+ @io.size > 0 ? load : clear
+ end
+
+ # somewhat similar to File.open, the open class method allows a block form where
+ # the Ole::Storage object is automatically closed on completion of the block.
+ def self.open arg, mode=nil, params={}
+ ole = new arg, mode, params
+ if block_given?
+ begin yield ole
+ ensure; ole.close
+ end
+ else ole
+ end
+ end
+
+ # load document from file.
+ #
+ # TODO: implement various allocationtable checks, maybe as a AllocationTable#fsck function :)
+ #
+ # 1. reterminate any chain not ending in EOC.
+ # compare file size with actually allocated blocks per file.
+ # 2. pass through all chain heads looking for collisions, and making sure nothing points to them
+ # (ie they are really heads). in both sbat and mbat
+ # 3. we know the locations of the bbat data, and mbat data. ensure that there are placeholder blocks
+ # in the bat for them.
+ # 4. maybe a check of excess data. if there is data outside the bbat.truncate.length + 1 * block_size,
+ # (eg what is used for truncate in #flush), then maybe add some sort of message about that. it
+ # will be automatically thrown away at close time.
+ def load
+ # we always read 512 for the header block. if the block size ends up being different,
+ # what happens to the 109 fat entries. are there more/less entries?
+ @io.rewind
+ header_block = @io.read 512
+ @header = Header.new header_block
+
+ # create an empty bbat.
+ @bbat = AllocationTable::Big.new self
+ bbat_chain = header_block[Header::SIZE..-1].unpack 'V*'
+ mbat_block = @header.mbat_start
+ @header.num_mbat.times do
+ blocks = @bbat.read([mbat_block]).unpack 'V*'
+ mbat_block = blocks.pop
+ bbat_chain += blocks
+ end
+ # am i using num_bat in the right way?
+ @bbat.load @bbat.read(bbat_chain[0, @header.num_bat])
+
+ # get block chain for directories, read it, then split it into chunks and load the
+ # directory entries. semantics changed - used to cut at first dir where dir.type == 0
+ @dirents = @bbat.read(@header.dirent_start).to_enum(:each_chunk, Dirent::SIZE).
+ map { |str| Dirent.new self, str }.reject { |d| d.type_id == 0 }
+
+ # now reorder from flat into a tree
+ # links are stored in some kind of balanced binary tree
+ # check that everything is visited at least, and at most once
+ # similarly with the blocks of the file.
+ # was thinking of moving this to Dirent.to_tree instead.
+ class << @dirents
+ def to_tree idx=0
+ return [] if idx == Dirent::EOT
+ d = self[idx]
+ d.children = to_tree d.child
+ raise FormatError, "directory #{d.inspect} used twice" if d.idx
+ d.idx = idx
+ to_tree(d.prev) + [d] + to_tree(d.next)
+ end
+ end
+
+ @root = @dirents.to_tree.first
+ Log.warn "root name was #{@root.name.inspect}" unless @root.name == 'Root Entry'
+ unused = @dirents.reject(&:idx).length
+ Log.warn "#{unused} unused directories" if unused > 0
+
+ # FIXME i don't currently use @header.num_sbat which i should
+ # hmm. nor do i write it. it means what exactly again?
+ # which mode to use here?
+ @sb_file = RangesIOResizeable.new @bbat, :first_block => @root.first_block, :size => @root.size
+ @sbat = AllocationTable::Small.new self
+ @sbat.load @bbat.read(@header.sbat_start)
+ end
+
+ def close
+ @sb_file.close
+ flush if @writeable
+ @io.close if @close_parent
+ end
+
+ # the flush method is the main "save" method. all file contents are always
+ # written directly to the file by the RangesIO objects, all this method does
+ # is write out all the file meta data - dirents, allocation tables, file header
+ # etc.
+ #
+ # maybe add an option to zero the padding, and any remaining avail blocks in the
+ # allocation table.
+ #
+ # TODO: long and overly complex. simplify and test better. eg, perhaps move serialization
+ # of bbat to AllocationTable::Big.
+ def flush
+ # update root dirent, and flatten dirent tree
+ @root.name = 'Root Entry'
+ @root.first_block = @sb_file.first_block
+ @root.size = @sb_file.size
+ @dirents = @root.flatten
+
+ # serialize the dirents using the bbat
+ RangesIOResizeable.open @bbat, 'w', :first_block => @header.dirent_start do |io|
+ @dirents.each { |dirent| io.write dirent.to_s }
+ padding = (io.size / @bbat.block_size.to_f).ceil * @bbat.block_size - io.size
+ io.write 0.chr * padding
+ @header.dirent_start = io.first_block
+ end
+
+ # serialize the sbat
+ # perhaps the blocks used by the sbat should be marked with BAT?
+ RangesIOResizeable.open @bbat, 'w', :first_block => @header.sbat_start do |io|
+ io.write @sbat.to_s
+ @header.sbat_start = io.first_block
+ @header.num_sbat = @bbat.chain(@header.sbat_start).length
+ end
+
+ # create RangesIOResizeable hooked up to the bbat. use that to claim bbat blocks using
+ # truncate. then when its time to write, convert that chain and some chunk of blocks at
+ # the end, into META_BAT blocks. write out the chain, and those meta bat blocks, and its
+ # done.
+ # this is perhaps not good, as we reclaim all bat blocks here, which
+ # may include the sbat we just wrote. FIXME
+ @bbat.map! do |b|
+ b == AllocationTable::BAT || b == AllocationTable::META_BAT ? AllocationTable::AVAIL : b
+ end
+
+ # currently we use a loop. this could be better, but basically,
+ # the act of writing out the bat, itself requires blocks which get
+ # recorded in the bat.
+ #
+ # i'm sure that there'd be some simpler closed form solution to this. solve
+ # recursive func:
+ #
+ # num_mbat_blocks = ceil(max((mbat_len - 109) * 4 / block_size, 0))
+ # bbat_len = initial_bbat_len + num_mbat_blocks
+ # mbat_len = ceil(bbat_len * 4 / block_size)
+ #
+ # the actual bbat allocation table is itself stored throughout the file, and that chain
+ # is stored in the initial blocks, and the mbat blocks.
+ num_mbat_blocks = 0
+ io = RangesIOResizeable.new @bbat, 'w', :first_block => AllocationTable::EOC
+ # truncate now, so that we can simplify size calcs - the mbat blocks will be appended in a
+ # contiguous chunk at the end.
+ # hmmm, i think this truncate should be matched with a truncate of the underlying io. if you
+ # delete a lot of stuff, and free up trailing blocks, the file size never shrinks. this can
+ # be fixed easily, add an io truncate
+ @bbat.truncate!
+ before = @io.size
+ @io.truncate @bbat.block_size * (@bbat.length + 1)
+ while true
+ # get total bbat size. equivalent to @bbat.to_s.length, but for the factoring in of
+ # the mbat blocks. we can't just add the mbat blocks directly to the bbat, as as this iteration
+ # progresses, more blocks may be needed for the bat itself (if there are no more gaps), and the
+ # mbat must remain contiguous.
+ bbat_data_len = ((@bbat.length + num_mbat_blocks) * 4 / @bbat.block_size.to_f).ceil * @bbat.block_size
+ # now storing the excess mbat blocks also increases the size of the bbat:
+ new_num_mbat_blocks = ([bbat_data_len / @bbat.block_size - 109, 0].max * 4 / (@bbat.block_size.to_f - 4)).ceil
+ if new_num_mbat_blocks != num_mbat_blocks
+ # need more space for the mbat.
+ num_mbat_blocks = new_num_mbat_blocks
+ elsif io.size != bbat_data_len
+ # need more space for the bat
+ # this may grow the bbat, depending on existing available blocks
+ io.truncate bbat_data_len
+ else
+ break
+ end
+ end
+
+ # now extract the info we want:
+ ranges = io.ranges
+ bbat_chain = @bbat.chain io.first_block
+ io.close
+ bbat_chain.each { |b| @bbat[b] = AllocationTable::BAT }
+ # tack on the mbat stuff
+ @header.num_bat = bbat_chain.length
+ mbat_blocks = (0...num_mbat_blocks).map do
+ block = @bbat.free_block
+ @bbat[block] = AllocationTable::META_BAT
+ block
+ end
+ @header.mbat_start = mbat_blocks.first || AllocationTable::EOC
+
+ # now finally write the bbat, using a not resizable io.
+ # the mode here will be 'r', which allows write atm.
+ RangesIO.open(@io, :ranges => ranges) { |f| f.write @bbat.to_s }
+
+ # this is the mbat. pad it out.
+ bbat_chain += [AllocationTable::AVAIL] * [109 - bbat_chain.length, 0].max
+ @header.num_mbat = num_mbat_blocks
+ if num_mbat_blocks != 0
+ # write out the mbat blocks now. first of all, where are they going to be?
+ mbat_data = bbat_chain[109..-1]
+ # expand the mbat_data to include the linked list forward pointers.
+ mbat_data = mbat_data.to_enum(:each_slice, @bbat.block_size / 4 - 1).to_a.
+ zip(mbat_blocks[1..-1] + [nil]).map { |a, b| b ? a + [b] : a }
+ # pad out the last one.
+ mbat_data.last.push(*([AllocationTable::AVAIL] * (@bbat.block_size / 4 - mbat_data.last.length)))
+ RangesIO.open @io, :ranges => @bbat.ranges(mbat_blocks) do |f|
+ f.write mbat_data.flatten.pack('V*')
+ end
+ end
+
+ # now seek back and write the header out
+ @io.seek 0
+ @io.write @header.to_s + bbat_chain[0, 109].pack('V*')
+ @io.flush
+ end
+
+ def clear
+ # initialize to equivalent of loading an empty ole document.
+ Log.warn 'creating new ole storage object on non-writable io' unless @writeable
+ @header = Header.new
+ @bbat = AllocationTable::Big.new self
+ @root = Dirent.new self, :type => :root, :name => 'Root Entry'
+ @dirents = [@root]
+ @root.idx = 0
+ @sb_file.close if @sb_file
+ @sb_file = RangesIOResizeable.new @bbat, :first_block => AllocationTable::EOC
+ @sbat = AllocationTable::Small.new self
+ # throw everything else the hell away
+ @io.truncate 0
+ end
+
+ # could be useful with mis-behaving ole documents. or to just clean them up.
+ def repack temp=:file
+ case temp
+ when :file
+ Tempfile.open 'ole-repack' do |io|
+ io.binmode
+ repack_using_io io
+ end
+ when :mem; StringIO.open('', &method(:repack_using_io))
+ else raise ArgumentError, "unknown temp backing #{temp.inspect}"
+ end
+ end
+
+ def repack_using_io temp_io
+ @io.rewind
+ IO.copy @io, temp_io
+ clear
+ Storage.open temp_io, nil, @params do |temp_ole|
+ #temp_ole.root.type = :dir
+ Dirent.copy temp_ole.root, root
+ end
+ end
+
+ def bat_for_size size
+ # note >=, not > previously.
+ size >= @header.threshold ? @bbat : @sbat
+ end
+
+ def inspect
+ "#<#{self.class} io=#{@io.inspect} root=#{@root.inspect}>"
+ end
+
+ #
+ # A class which wraps the ole header
+ #
+ # Header.new can be both used to load from a string, or to create from
+ # defaults. Serialization is accomplished with the #to_s method.
+ #
+ class Header < Struct.new(
+ :magic, :clsid, :minor_ver, :major_ver, :byte_order, :b_shift, :s_shift,
+ :reserved, :csectdir, :num_bat, :dirent_start, :transacting_signature, :threshold,
+ :sbat_start, :num_sbat, :mbat_start, :num_mbat
+ )
+ PACK = 'a8 a16 v2 a2 v2 a6 V3 a4 V5'
+ SIZE = 0x4c
+ # i have seen it pointed out that the first 4 bytes of hex,
+ # 0xd0cf11e0, is supposed to spell out docfile. hmmm :)
+ MAGIC = "\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" # expected value of Header#magic
+ # what you get if creating new header from scratch.
+ # AllocationTable::EOC isn't available yet. meh.
+ EOC = 0xfffffffe
+ DEFAULT = [
+ MAGIC, 0.chr * 16, 59, 3, "\xfe\xff", 9, 6,
+ 0.chr * 6, 0, 1, EOC, 0.chr * 4,
+ 4096, EOC, 0, EOC, 0
+ ]
+
+ def initialize values=DEFAULT
+ values = values.unpack(PACK) if String === values
+ super(*values)
+ validate!
+ end
+
+ def to_s
+ to_a.pack PACK
+ end
+
+ def validate!
+ raise FormatError, "OLE2 signature is invalid" unless magic == MAGIC
+ if num_bat == 0 or # is that valid for a completely empty file?
+ # not sure about this one. basically to do max possible bat given size of mbat
+ num_bat > 109 && num_bat > 109 + num_mbat * (1 << b_shift - 2) or
+ # shouldn't need to use the mbat as there is enough space in the header block
+ num_bat < 109 && num_mbat != 0 or
+ # given the size of the header is 76, if b_shift <= 6, blocks address the header.
+ s_shift > b_shift or b_shift <= 6 or b_shift >= 31 or
+ # we only handle little endian
+ byte_order != "\xfe\xff"
+ raise FormatError, "not valid OLE2 structured storage file"
+ end
+ # relaxed this, due to test-msg/qwerty_[1-3]*.msg they all had
+ # 3 for this value.
+ # transacting_signature != "\x00" * 4 or
+ if threshold != 4096 or
+ num_mbat == 0 && mbat_start != AllocationTable::EOC or
+ reserved != "\x00" * 6
+ Log.warn "may not be a valid OLE2 structured storage file"
+ end
+ true
+ end
+ end
+
+ #
+ # +AllocationTable+'s hold the chains corresponding to files. Given
+ # an initial index, <tt>AllocationTable#chain</tt> follows the chain, returning
+ # the blocks that make up that file.
+ #
+ # There are 2 allocation tables, the bbat, and sbat, for big and small
+ # blocks respectively. The block chain should be loaded using either
+ # <tt>Storage#read_big_blocks</tt> or <tt>Storage#read_small_blocks</tt>
+ # as appropriate.
+ #
+ # Whether or not big or small blocks are used for a file depends on
+ # whether its size is over the <tt>Header#threshold</tt> level.
+ #
+ # An <tt>Ole::Storage</tt> document is serialized as a series of directory objects,
+ # which are stored in blocks throughout the file. The blocks are either
+ # big or small, and are accessed using the <tt>AllocationTable</tt>.
+ #
+ # The bbat allocation table's data is stored in the spare room in the header
+ # block, and in extra blocks throughout the file as referenced by the meta
+ # bat. That chain is linear, as there is no higher level table.
+ #
+ # AllocationTable.new is used to create an empty table. It can parse a string
+ # with the #load method. Serialization is accomplished with the #to_s method.
+ #
+ class AllocationTable < Array
+ # a free block (I don't currently leave any blocks free), although I do pad out
+ # the allocation table with AVAIL to the block size.
+ AVAIL = 0xffffffff
+ EOC = 0xfffffffe # end of a chain
+ # these blocks are used for storing the allocation table chains
+ BAT = 0xfffffffd
+ META_BAT = 0xfffffffc
+
+ attr_reader :ole, :io, :block_size
+ def initialize ole
+ @ole = ole
+ @sparse = true
+ super()
+ end
+
+ def load data
+ replace data.unpack('V*')
+ end
+
+ def truncate
+ # this strips trailing AVAILs. come to think of it, this has the potential to break
+ # bogus ole. if you terminate using AVAIL instead of EOC, like I did before. but that is
+ # very broken. however, if a chain ends with AVAIL, it should probably be fixed to EOC
+ # at load time.
+ temp = reverse
+ not_avail = temp.find { |b| b != AVAIL } and temp = temp[temp.index(not_avail)..-1]
+ temp.reverse
+ end
+
+ def truncate!
+ replace truncate
+ end
+
+ def to_s
+ table = truncate
+ # pad it out some
+ num = @ole.bbat.block_size / 4
+ # do you really use AVAIL? they probably extend past end of file, and may shortly
+ # be used for the bat. not really good.
+ table += [AVAIL] * (num - (table.length % num)) if (table.length % num) != 0
+ table.pack 'V*'
+ end
+
+ # rewrote this to be non-recursive as it broke on a large attachment
+ # chain with a stack error
+ def chain idx
+ a = []
+ until idx >= META_BAT
+ raise FormatError, "broken allocationtable chain" if idx < 0 || idx > length
+ a << idx
+ idx = self[idx]
+ end
+ Log.warn "invalid chain terminator #{idx}" unless idx == EOC
+ a
+ end
+
+ # Turn a chain (an array given by +chain+) of blocks (optionally
+ # truncated to +size+) into an array of arrays describing the stretches of
+ # bytes in the file that it belongs to.
+ #
+ # The blocks are Big or Small blocks depending on the table type.
+ def blocks_to_ranges chain, size=nil
+ # truncate the chain if required
+ chain = chain[0...(size.to_f / block_size).ceil] if size
+ # convert chain to ranges of the block size
+ ranges = chain.map { |i| [block_size * i, block_size] }
+ # truncate final range if required
+ ranges.last[1] -= (ranges.length * block_size - size) if ranges.last and size
+ ranges
+ end
+
+ def ranges chain, size=nil
+ chain = self.chain(chain) unless Array === chain
+ blocks_to_ranges chain, size
+ end
+
+ # quick shortcut. chain can be either a head (in which case the table is used to
+ # turn it into a chain), or a chain. it is converted to ranges, then to rangesio.
+ def open chain, size=nil, &block
+ RangesIO.open @io, :ranges => ranges(chain, size), &block
+ end
+
+ def read chain, size=nil
+ open chain, size, &:read
+ end
+
+ # catch any method that may add an AVAIL somewhere in the middle, thus invalidating
+ # the @sparse speedup for free_block. annoying using eval, but define_method won't
+ # work for this.
+ # FIXME
+ [:map!, :collect!].each do |name|
+ eval <<-END
+ def #{name}(*args, &block)
+ @sparse = true
+ super
+ end
+ END
+ end
+
+ def []= idx, val
+ @sparse = true if val == AVAIL
+ super
+ end
+
+ def free_block
+ if @sparse
+ i = index(AVAIL) and return i
+ end
+ @sparse = false
+ push AVAIL
+ length - 1
+ end
+
+ # must return first_block. modifies +blocks+ in place
+ def resize_chain blocks, size
+ new_num_blocks = (size / block_size.to_f).ceil
+ old_num_blocks = blocks.length
+ if new_num_blocks < old_num_blocks
+ # de-allocate some of our old blocks. TODO maybe zero them out in the file???
+ (new_num_blocks...old_num_blocks).each { |i| self[blocks[i]] = AVAIL }
+ self[blocks[new_num_blocks-1]] = EOC if new_num_blocks > 0
+ blocks.slice! new_num_blocks..-1
+ elsif new_num_blocks > old_num_blocks
+ # need some more blocks.
+ last_block = blocks.last
+ (new_num_blocks - old_num_blocks).times do
+ block = free_block
+ # connect the chain. handle corner case of blocks being [] initially
+ self[last_block] = block if last_block
+ blocks << block
+ last_block = block
+ self[last_block] = EOC
+ end
+ end
+ # update ranges, and return that also now
+ blocks
+ end
+
+ class Big < AllocationTable
+ def initialize(*args)
+ super
+ @block_size = 1 << @ole.header.b_shift
+ @io = @ole.io
+ end
+
+ # Big blocks are kind of -1 based, in order to not clash with the header.
+ def blocks_to_ranges blocks, size
+ super blocks.map { |b| b + 1 }, size
+ end
+ end
+
+ class Small < AllocationTable
+ def initialize(*args)
+ super
+ @block_size = 1 << @ole.header.s_shift
+ @io = @ole.sb_file
+ end
+ end
+ end
+
+ # like normal RangesIO, but Ole::Storage specific. the ranges are backed by an
+ # AllocationTable, and can be resized. used for read/write to 2 streams:
+ # 1. serialized dirent data
+ # 2. sbat table data
+ # 3. all dirents but through RangesIOMigrateable below
+ #
+ # Note that all internal access to first_block is through accessors, as it is sometimes
+ # useful to redirect it.
+ class RangesIOResizeable < RangesIO
+ attr_reader :bat
+ attr_accessor :first_block
+ def initialize bat, mode='r', params={}
+ mode, params = 'r', mode if Hash === mode
+ first_block, size = params.values_at :first_block, :size
+ raise ArgumentError, 'must specify first_block' unless first_block
+ @bat = bat
+ self.first_block = first_block
+ # we now cache the blocks chain, for faster resizing.
+ @blocks = @bat.chain first_block
+ super @bat.io, mode, :ranges => @bat.ranges(@blocks, size)
+ end
+
+ def truncate size
+ # note that old_blocks is != @ranges.length necessarily. i'm planning to write a
+ # merge_ranges function that merges sequential ranges into one as an optimization.
+ @bat.resize_chain @blocks, size
+ @ranges = @bat.ranges @blocks, size
+ @pos = @size if @pos > size
+ self.first_block = @blocks.empty? ? AllocationTable::EOC : @blocks.first
+
+ # don't know if this is required, but we explicitly request our @io to grow if necessary
+ # we never shrink it though. maybe this belongs in allocationtable, where smarter decisions
+ # can be made.
+ # maybe its ok to just seek out there later??
+ max = @ranges.map { |pos, len| pos + len }.max || 0
+ @io.truncate max if max > @io.size
+
+ @size = size
+ end
+ end
+
+ # like RangesIOResizeable, but Ole::Storage::Dirent specific. provides for migration
+ # between bats based on size, and updating the dirent.
+ class RangesIOMigrateable < RangesIOResizeable
+ attr_reader :dirent
+ def initialize dirent, mode='r'
+ @dirent = dirent
+ super @dirent.ole.bat_for_size(@dirent.size), mode,
+ :first_block => @dirent.first_block, :size => @dirent.size
+ end
+
+ def truncate size
+ bat = @dirent.ole.bat_for_size size
+ if bat.class != @bat.class
+ # bat migration needed! we need to backup some data. the amount of data
+ # should be <= @ole.header.threshold, so we can just hold it all in one buffer.
+ # backup this
+ pos = @pos
+ @pos = 0
+ keep = read [@size, size].min
+ # this does a normal truncate to 0, removing our presence from the old bat, and
+ # rewrite the dirent's first_block
+ super 0
+ @bat = bat
+ # just change the underlying io from right under everyone :)
+ @io = bat.io
+ # important to do this now, before the write. as the below write will always
+ # migrate us back to sbat! this will now allocate us +size+ in the new bat.
+ super
+ @pos = 0
+ write keep
+ @pos = pos
+ else
+ super
+ end
+ # now just update the file
+ @dirent.size = size
+ end
+
+ # forward this to the dirent
+ def first_block
+ @dirent.first_block
+ end
+
+ def first_block= val
+ @dirent.first_block = val
+ end
+ end
+
+ #
+ # A class which wraps an ole directory entry. Can be either a directory
+ # (<tt>Dirent#dir?</tt>) or a file (<tt>Dirent#file?</tt>)
+ #
+ # Most interaction with <tt>Ole::Storage</tt> is through this class.
+ # The 2 most important functions are <tt>Dirent#children</tt>, and
+ # <tt>Dirent#data</tt>.
+ #
+ # was considering separate classes for dirs and files. some methods/attrs only
+ # applicable to one or the other.
+ #
+ # As with the other classes, #to_s performs the serialization.
+ #
+ class Dirent < Struct.new(
+ :name_utf16, :name_len, :type_id, :colour, :prev, :next, :child,
+ :clsid, :flags, # dirs only
+ :create_time_str, :modify_time_str, # files only
+ :first_block, :size, :reserved
+ )
+ include RecursivelyEnumerable
+
+ PACK = 'a64 v C C V3 a16 V a8 a8 V2 a4'
+ SIZE = 128
+ TYPE_MAP = {
+ # this is temporary
+ 0 => :empty,
+ 1 => :dir,
+ 2 => :file,
+ 5 => :root
+ }
+ # something to do with the fact that the tree is supposed to be red-black
+ COLOUR_MAP = {
+ 0 => :red,
+ 1 => :black
+ }
+ # used in the next / prev / child stuff to show that the tree ends here.
+ # also used for first_block for directory.
+ EOT = 0xffffffff
+ DEFAULT = [
+ 0.chr * 2, 2, 0, # will get overwritten
+ 1, EOT, EOT, EOT,
+ 0.chr * 16, 0, nil, nil,
+ AllocationTable::EOC, 0, 0.chr * 4
+ ]
+
+ # i think its just used by the tree building
+ attr_accessor :idx
+ # This returns all the children of this +Dirent+. It is filled in
+ # when the tree structure is recreated.
+ attr_accessor :children
+ attr_accessor :name
+ attr_reader :ole, :type, :create_time, :modify_time
+ def initialize ole, values=DEFAULT, params={}
+ @ole = ole
+ values, params = DEFAULT, values if Hash === values
+ values = values.unpack(PACK) if String === values
+ super(*values)
+
+ # extra parsing from the actual struct values
+ @name = params[:name] || Types::Variant.load(Types::VT_LPWSTR, name_utf16[0...name_len])
+ @type = if params[:type]
+ unless TYPE_MAP.values.include?(params[:type])
+ raise ArgumentError, "unknown type #{params[:type].inspect}"
+ end
+ params[:type]
+ else
+ TYPE_MAP[type_id] or raise FormatError, "unknown type_id #{type_id.inspect}"
+ end
+
+ # further extra type specific stuff
+ if file?
+ default_time = @ole.params[:update_timestamps] ? Time.now : nil
+ @create_time ||= default_time
+ @modify_time ||= default_time
+ @create_time = Types::Variant.load(Types::VT_FILETIME, create_time_str) if create_time_str
+ @modify_time = Types::Variant.load(Types::VT_FILETIME, create_time_str) if modify_time_str
+ @children = nil
+ else
+ @create_time = nil
+ @modify_time = nil
+ self.size = 0 unless @type == :root
+ @children = []
+ end
+
+ # to silence warnings. used for tree building at load time
+ # only.
+ @idx = nil
+ end
+
+ def open mode='r'
+ raise Errno::EISDIR unless file?
+ io = RangesIOMigrateable.new self, mode
+ # TODO work on the mode string stuff a bit more.
+ # maybe let the io object know about the mode, so it can refuse
+ # to work for read/write appropriately. maybe redefine all unusable
+ # methods using singleton class to throw errors.
+ # for now, i just want to implement truncation on use of 'w'. later,
+ # i need to do 'a' etc.
+ case mode
+ when 'r', 'r+'
+ # as i don't enforce reading/writing, nothing changes here. kind of
+ # need to enforce tt if i want modify times to work better.
+ @modify_time = Time.now if mode == 'r+'
+ when 'w'
+ @modify_time = Time.now
+ # io.truncate 0
+ #else
+ # raise NotImplementedError, "unsupported mode - #{mode.inspect}"
+ end
+ if block_given?
+ begin yield io
+ ensure; io.close
+ end
+ else io
+ end
+ end
+
+ def read limit=nil
+ open { |io| io.read limit }
+ end
+
+ def file?
+ type == :file
+ end
+
+ def dir?
+ # to count root as a dir.
+ !file?
+ end
+
+ # maybe need some options regarding case sensitivity.
+ def / name
+ children.find { |child| name === child.name }
+ end
+
+ def [] idx
+ if String === idx
+ #warn 'String form of Dirent#[] is deprecated'
+ self / idx
+ else
+ super
+ end
+ end
+
+ # move to ruby-msg. and remove from here
+ def time
+ #warn 'Dirent#time is deprecated'
+ create_time || modify_time
+ end
+
+ def each_child(&block)
+ @children.each(&block)
+ end
+
+ # flattens the tree starting from here into +dirents+. note it modifies its argument.
+ def flatten dirents=[]
+ @idx = dirents.length
+ dirents << self
+ if file?
+ self.prev = self.next = self.child = EOT
+ else
+ children.each { |child| child.flatten dirents }
+ self.child = Dirent.flatten_helper children
+ end
+ dirents
+ end
+
+ # i think making the tree structure optimized is actually more complex than this, and
+ # requires some intelligent ordering of the children based on names, but as long as
+ # it is valid its ok.
+ # actually, i think its ok. gsf for example only outputs a singly-linked-list, where
+ # prev is always EOT.
+ def self.flatten_helper children
+ return EOT if children.empty?
+ i = children.length / 2
+ this = children[i]
+ this.prev, this.next = [(0...i), (i+1..-1)].map { |r| flatten_helper children[r] }
+ this.idx
+ end
+
+ def to_s
+ tmp = Types::Variant.dump(Types::VT_LPWSTR, name)
+ tmp = tmp[0, 62] if tmp.length > 62
+ tmp += 0.chr * 2
+ self.name_len = tmp.length
+ self.name_utf16 = tmp + 0.chr * (64 - tmp.length)
+ # type_id can perhaps be set in the initializer, as its read only now.
+ self.type_id = TYPE_MAP.to_a.find { |id, name| @type == name }.first
+ # for the case of files, it is assumed that that was handled already
+ # note not dir?, so as not to override root's first_block
+ self.first_block = Dirent::EOT if type == :dir
+ if file?
+ # this is messed up. it changes the time stamps regardless of whether the file
+ # was actually touched. instead, any open call with a writeable mode, should update
+ # the modify time. create time would be set in new.
+ if @ole.params[:update_timestamps]
+ self.create_time_str = Types::Variant.dump Types::VT_FILETIME, @create_time
+ self.modify_time_str = Types::Variant.dump Types::VT_FILETIME, @modify_time
+ end
+ else
+ self.create_time_str = 0.chr * 8
+ self.modify_time_str = 0.chr * 8
+ end
+ to_a.pack PACK
+ end
+
+ def inspect
+ str = "#<Dirent:#{name.inspect}"
+ # perhaps i should remove the data snippet. its not that useful anymore.
+ # there is also some dir specific stuff. like clsid, flags, that i should
+ # probably include
+ if file?
+ tmp = read 9
+ data = tmp.length == 9 ? tmp[0, 5] + '...' : tmp
+ str << " size=#{size}" +
+ "#{modify_time ? ' modify_time=' + modify_time.to_s.inspect : nil}" +
+ " data=#{data.inspect}"
+ end
+ str + '>'
+ end
+
+ def delete child
+ # remove from our child array, so that on reflatten and re-creation of @dirents, it will be gone
+ raise ArgumentError, "#{child.inspect} not a child of #{self.inspect}" unless @children.delete child
+ # free our blocks
+ child.open { |io| io.truncate 0 }
+ end
+
+ def self.copy src, dst
+ # copies the contents of src to dst. must be the same type. this will throw an
+ # error on copying to root. maybe this will recurse too much for big documents??
+ raise ArgumentError, 'differing types' if src.file? and !dst.file?
+ dst.name = src.name
+ if src.dir?
+ src.children.each do |src_child|
+ dst_child = Dirent.new dst.ole, :type => src_child.type
+ dst.children << dst_child
+ Dirent.copy src_child, dst_child
+ end
+ else
+ src.open do |src_io|
+ dst.open { |dst_io| IO.copy src_io, dst_io }
+ end
+ end
+ end
+ end
+ end
+end
+
diff --git a/vendor/ruby-ole/lib/ole/storage/file_system.rb b/vendor/ruby-ole/lib/ole/storage/file_system.rb
new file mode 100644
index 000000000..531f1ba11
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/storage/file_system.rb
@@ -0,0 +1,423 @@
+#
+# = Introduction
+#
+# This file intends to provide file system-like api support, a la <tt>zip/zipfilesystem</tt>.
+#
+# = TODO
+#
+# - need to implement some more IO functions on RangesIO, like #puts, #print
+# etc, like AbstractOutputStream from zipfile.
+#
+# - check Dir.mkdir, and File.open, and File.rename, to add in filename
+# length checks (max 32 / 31 or something).
+# do the automatic truncation, and add in any necessary warnings.
+#
+# - File.split('a/') == File.split('a') == ['.', 'a']
+# the implication of this, is that things that try to force directory
+# don't work. like, File.rename('a', 'b'), should work if a is a file
+# or directory, but File.rename('a/', 'b') should only work if a is
+# a directory. tricky, need to clean things up a bit more.
+# i think a general path name => dirent method would work, with flags
+# about what should raise an error.
+#
+# - Need to look at streamlining things after getting all the tests passing,
+# as this file's getting pretty long - almost half the real implementation.
+# and is probably more inefficient than necessary.
+# too many exceptions in the expected path of certain functions.
+#
+# - should look at profiles before and after switching ruby-msg to use
+# the filesystem api.
+#
+
+require 'ole/storage'
+
+module Ole # :nodoc:
+ class Storage
+ def file
+ @file ||= FileClass.new self
+ end
+
+ def dir
+ @dir ||= DirClass.new self
+ end
+
+ # tries to get a dirent for path. return nil if it doesn't exist
+ # (change it)
+ def dirent_from_path path
+ dirent = @root
+ path = file.expand_path path
+ path = path.sub(/^\/*/, '').sub(/\/*$/, '').split(/\/+/)
+ until path.empty?
+ return nil if dirent.file?
+ return nil unless dirent = dirent/path.shift
+ end
+ dirent
+ end
+
+ class FileClass
+ class Stat
+ attr_reader :ftype, :size, :blocks, :blksize
+ attr_reader :nlink, :uid, :gid, :dev, :rdev, :ino
+ def initialize dirent
+ @dirent = dirent
+ @size = dirent.size
+ if file?
+ @ftype = 'file'
+ bat = dirent.ole.bat_for_size(dirent.size)
+ @blocks = bat.chain(dirent.first_block).length
+ @blksize = bat.block_size
+ else
+ @ftype = 'directory'
+ @blocks = 0
+ @blksize = 0
+ end
+ # a lot of these are bogus. ole file format has no analogs
+ @nlink = 1
+ @uid, @gid = 0, 0
+ @dev, @rdev = 0, 0
+ @ino = 0
+ # need to add times - atime, mtime, ctime.
+ end
+
+ alias rdev_major :rdev
+ alias rdev_minor :rdev
+
+ def file?
+ @dirent.file?
+ end
+
+ def directory?
+ @dirent.dir?
+ end
+
+ def size?
+ size if file?
+ end
+
+ def inspect
+ pairs = (instance_variables - ['@dirent']).map do |n|
+ "#{n[1..-1]}=#{instance_variable_get n}"
+ end
+ "#<#{self.class} #{pairs * ', '}>"
+ end
+ end
+
+ def initialize ole
+ @ole = ole
+ end
+
+ def expand_path path
+ # get the raw stored pwd value (its blank for root)
+ pwd = @ole.dir.instance_variable_get :@pwd
+ # its only absolute if it starts with a '/'
+ path = "#{pwd}/#{path}" unless path =~ /^\//
+ # at this point its already absolute. we use File.expand_path
+ # just for the .. and . handling
+ # No longer use RUBY_PLATFORM =~ /win/ as it matches darwin. better way?
+ File.expand_path(path)[File::ALT_SEPARATOR == "\\" ? (2..-1) : (0..-1)]
+ end
+
+ # +orig_path+ is just so that we can use the requested path
+ # in the error messages even if it has been already modified
+ def dirent_from_path path, orig_path=nil
+ orig_path ||= path
+ dirent = @ole.dirent_from_path path
+ raise Errno::ENOENT, orig_path unless dirent
+ raise Errno::EISDIR, orig_path if dirent.dir?
+ dirent
+ end
+ private :dirent_from_path
+
+ def exists? path
+ !!@ole.dirent_from_path(path)
+ end
+ alias exist? :exists?
+
+ def file? path
+ dirent = @ole.dirent_from_path path
+ dirent and dirent.file?
+ end
+
+ def directory? path
+ dirent = @ole.dirent_from_path path
+ dirent and dirent.dir?
+ end
+
+ def open path, mode='r', &block
+ if IO::Mode.new(mode).create?
+ begin
+ dirent = dirent_from_path path
+ rescue Errno::ENOENT
+ # maybe instead of repeating this everywhere, i should have
+ # a get_parent_dirent function.
+ parent_path, basename = File.split expand_path(path)
+ parent = @ole.dir.send :dirent_from_path, parent_path, path
+ parent.children << dirent = Dirent.new(@ole, :type => :file, :name => basename)
+ end
+ else
+ dirent = dirent_from_path path
+ end
+ dirent.open mode, &block
+ end
+
+ # explicit wrapper instead of alias to inhibit block
+ def new path, mode='r'
+ open path, mode
+ end
+
+ def size path
+ dirent_from_path(path).size
+ rescue Errno::EISDIR
+ # kind of arbitrary. I'm getting 4096 from ::File, but
+ # the zip tests want 0.
+ 0
+ end
+
+ def size? path
+ dirent_from_path(path).size
+ # any other exceptions i need to rescue?
+ rescue Errno::ENOENT, Errno::EISDIR
+ nil
+ end
+
+ def stat path
+ # we do this to allow dirs.
+ dirent = @ole.dirent_from_path path
+ raise Errno::ENOENT, path unless dirent
+ Stat.new dirent
+ end
+
+ def read path
+ open path, &:read
+ end
+
+ # most of the work this function does is moving the dirent between
+ # 2 parents. the actual name changing is quite simple.
+ # File.rename can move a file into another folder, which is why i've
+ # done it too, though i think its not always possible...
+ #
+ # FIXME File.rename can be used for directories too....
+ def rename from_path, to_path
+ # check what we want to rename from exists. do it this
+ # way to allow directories.
+ dirent = @ole.dirent_from_path from_path
+ raise Errno::ENOENT, from_path unless dirent
+ # delete what we want to rename to if necessary
+ begin
+ unlink to_path
+ rescue Errno::ENOENT
+ # we actually get here, but rcov doesn't think so. add 1 + 1 to
+ # keep rcov happy for now... :)
+ 1 + 1
+ end
+ # reparent the dirent
+ from_parent_path, from_basename = File.split expand_path(from_path)
+ to_parent_path, to_basename = File.split expand_path(to_path)
+ from_parent = @ole.dir.send :dirent_from_path, from_parent_path, from_path
+ to_parent = @ole.dir.send :dirent_from_path, to_parent_path, to_path
+ from_parent.children.delete dirent
+ # and also change its name
+ dirent.name = to_basename
+ to_parent.children << dirent
+ 0
+ end
+
+ # crappy copy from Dir.
+ def unlink(*paths)
+ paths.each do |path|
+ dirent = @ole.dirent_from_path path
+ # i think we should free all of our blocks from the
+ # allocation table.
+ # i think if you run repack, all free blocks should get zeroed,
+ # but currently the original data is there unmodified.
+ open(path) { |f| f.truncate 0 }
+ # remove ourself from our parent, so we won't be part of the dir
+ # tree at save time.
+ parent_path, basename = File.split expand_path(path)
+ parent = @ole.dir.send :dirent_from_path, parent_path, path
+ parent.children.delete dirent
+ end
+ paths.length # hmmm. as per ::File ?
+ end
+ alias delete :unlink
+ end
+
+ #
+ # an *instance* of this class is supposed to provide similar methods
+ # to the class methods of Dir itself.
+ #
+ # pretty complete. like zip/zipfilesystem's implementation, i provide
+ # everything except chroot and glob. glob could be done with a glob
+ # to regex regex, and then simply match in the entries array... although
+ # recursive glob complicates that somewhat.
+ #
+ # Dir.chroot, Dir.glob, Dir.[], and Dir.tmpdir is the complete list.
+ class DirClass
+ def initialize ole
+ @ole = ole
+ @pwd = ''
+ end
+
+ # +orig_path+ is just so that we can use the requested path
+ # in the error messages even if it has been already modified
+ def dirent_from_path path, orig_path=nil
+ orig_path ||= path
+ dirent = @ole.dirent_from_path path
+ raise Errno::ENOENT, orig_path unless dirent
+ raise Errno::ENOTDIR, orig_path unless dirent.dir?
+ dirent
+ end
+ private :dirent_from_path
+
+ def open path
+ dir = Dir.new path, entries(path)
+ if block_given?
+ yield dir
+ else
+ dir
+ end
+ end
+
+ # as for file, explicit alias to inhibit block
+ def new path
+ open path
+ end
+
+ # pwd is always stored without the trailing slash. we handle
+ # the root case here
+ def pwd
+ if @pwd.empty?
+ '/'
+ else
+ @pwd
+ end
+ end
+ alias getwd :pwd
+
+ def chdir orig_path
+ # make path absolute, squeeze slashes, and remove trailing slash
+ path = @ole.file.expand_path(orig_path).gsub(/\/+/, '/').sub(/\/$/, '')
+ # this is just for the side effects of the exceptions if invalid
+ dirent_from_path path, orig_path
+ if block_given?
+ old_pwd = @pwd
+ begin
+ @pwd = path
+ yield
+ ensure
+ @pwd = old_pwd
+ end
+ else
+ @pwd = path
+ 0
+ end
+ end
+
+ def entries path
+ dirent = dirent_from_path path
+ # Not sure about adding on the dots...
+ entries = %w[. ..] + dirent.children.map(&:name)
+ # do some checks about un-reachable files
+ seen = {}
+ entries.each do |n|
+ Log.warn "inaccessible file (filename contains slash) - #{n.inspect}" if n['/']
+ Log.warn "inaccessible file (duplicate filename) - #{n.inspect}" if seen[n]
+ seen[n] = true
+ end
+ entries
+ end
+
+ def foreach path, &block
+ entries(path).each(&block)
+ end
+
+ # there are some other important ones, like:
+ # chroot (!), glob etc etc. for now, i think
+ def mkdir path
+ # as for rmdir below:
+ parent_path, basename = File.split @ole.file.expand_path(path)
+ # note that we will complain about the full path despite accessing
+ # the parent path. this is consistent with ::Dir
+ parent = dirent_from_path parent_path, path
+ # now, we first should ensure that it doesn't already exist
+ # either as a file or a directory.
+ raise Errno::EEXIST, path if parent/basename
+ parent.children << Dirent.new(@ole, :type => :dir, :name => basename)
+ 0
+ end
+
+ def rmdir path
+ dirent = dirent_from_path path
+ raise Errno::ENOTEMPTY, path unless dirent.children.empty?
+
+ # now delete it, how to do that? the canonical representation that is
+ # maintained is the root tree, and the children array. we must remove it
+ # from the children array.
+ # we need the parent then. this sucks but anyway:
+ # we need to split the path. but before we can do that, we need
+ # to expand it first. eg. say we need the parent to unlink
+ # a/b/../c. the parent should be a, not a/b/.., or a/b.
+ parent_path, basename = File.split @ole.file.expand_path(path)
+ # this shouldn't be able to fail if the above didn't
+ parent = dirent_from_path parent_path
+ # note that the way this currently works, on save and repack time this will get
+ # reflected. to work properly, ie to make a difference now it would have to re-write
+ # the dirent. i think that Ole::Storage#close will handle that. and maybe include a
+ # #repack.
+ parent.children.delete dirent
+ 0 # hmmm. as per ::Dir ?
+ end
+ alias delete :rmdir
+ alias unlink :rmdir
+
+ # note that there is nothing remotely ole specific about
+ # this class. it simply provides the dir like sequential access
+ # methods on top of an array.
+ # hmm, doesn't throw the IOError's on use of a closed directory...
+ class Dir
+ include Enumerable
+
+ attr_reader :path
+ def initialize path, entries
+ @path, @entries, @pos = path, entries, 0
+ @closed = false
+ end
+
+ def pos
+ raise IOError if @closed
+ @pos
+ end
+
+ def each(&block)
+ raise IOError if @closed
+ @entries.each(&block)
+ end
+
+ def close
+ @closed = true
+ end
+
+ def read
+ raise IOError if @closed
+ @entries[pos]
+ ensure
+ @pos += 1 if pos < @entries.length
+ end
+
+ def pos= pos
+ raise IOError if @closed
+ @pos = [[0, pos].max, @entries.length].min
+ end
+
+ def rewind
+ raise IOError if @closed
+ @pos = 0
+ end
+
+ alias tell :pos
+ alias seek :pos=
+ end
+ end
+ end
+end
+
diff --git a/vendor/ruby-ole/lib/ole/storage/meta_data.rb b/vendor/ruby-ole/lib/ole/storage/meta_data.rb
new file mode 100644
index 000000000..be84037df
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/storage/meta_data.rb
@@ -0,0 +1,148 @@
+require 'ole/types/property_set'
+
+module Ole
+ class Storage
+ #
+ # The MetaData class is designed to be high level interface to all the
+ # underlying meta data stored within different sections, themselves within
+ # different property set streams.
+ #
+ # With this class, you can simply get properties using their names, without
+ # needing to know about the underlying guids, property ids etc.
+ #
+ # Example:
+ #
+ # Ole::Storage.open('test.doc') { |ole| p ole.meta_data.doc_author }
+ #
+ # TODO:
+ #
+ # * add write support
+ # * fix some of the missing type coercion (eg FileTime)
+ # * maybe add back the ability to access individual property sets as a unit
+ # directly. ie <tt>ole.summary_information</tt>. Is this useful?
+ # * full key support, for unknown keys, like
+ # <tt>ole.meta_data[myguid, myid]</tt>. probably needed for user-defined
+ # properties too.
+ #
+ class MetaData
+ include Enumerable
+
+ FILE_MAP = {
+ Types::PropertySet::FMTID_SummaryInformation => "\005SummaryInformation",
+ Types::PropertySet::FMTID_DocSummaryInfo => "\005DocumentSummaryInformation"
+ }
+
+ FORMAT_MAP = {
+ 'MSWordDoc' => :doc
+ }
+
+ CLSID_EXCEL97 = Types::Clsid.parse "{00020820-0000-0000-c000-000000000046}"
+ CLSID_EXCEL95 = Types::Clsid.parse "{00020810-0000-0000-c000-000000000046}"
+ CLSID_WORD97 = Types::Clsid.parse "{00020906-0000-0000-c000-000000000046}"
+ CLSID_WORD95 = Types::Clsid.parse "{00020900-0000-0000-c000-000000000046}"
+
+ CLSID_MAP = {
+ CLSID_EXCEL97 => :xls,
+ CLSID_EXCEL95 => :xls,
+ CLSID_WORD97 => :doc,
+ CLSID_WORD95 => :doc
+ }
+
+ MIME_TYPES = {
+ :xls => 'application/vnd.ms-excel',
+ :doc => 'application/msword',
+ :ppt => 'application/vnd.ms-powerpoint',
+ # not registered at IANA, but seems most common usage
+ :msg => 'application/vnd.ms-outlook',
+ # this is my default fallback option. also not registered at IANA.
+ # file(1)'s default is application/msword, which is useless...
+ nil => 'application/x-ole-storage'
+ }
+
+ def initialize ole
+ @ole = ole
+ end
+
+ # i'm thinking of making file_format and mime_type available through
+ # #[], #each, and #to_h also, as calculated meta data (not assignable)
+
+ def comp_obj
+ return {} unless dirent = @ole.root["\001CompObj"]
+ data = dirent.read
+ # see - https://gnunet.org/svn/Extractor/doc/StarWrite_File_Format.html
+ # compobj_version: 0x0001
+ # byte_order: 0xffe
+ # windows_version: 0x00000a03 (win31 apparently)
+ # marker: 0xffffffff
+ compobj_version, byte_order, windows_version, marker, clsid =
+ data.unpack("vvVVa#{Types::Clsid::SIZE}")
+ strings = []
+ i = 28
+ while i < data.length
+ len = data[i, 4].unpack('V').first
+ i += 4
+ strings << data[i, len - 1]
+ i += len
+ end
+ # in the unknown chunk, you usually see something like 'Word.Document.6'
+ {:username => strings[0], :file_format => strings[1], :unknown => strings[2..-1]}
+ end
+ private :comp_obj
+
+ def file_format
+ comp_obj[:file_format]
+ end
+
+ def mime_type
+ # based on the CompObj stream contents
+ type = FORMAT_MAP[file_format]
+ return MIME_TYPES[type] if type
+
+ # based on the root clsid
+ type = CLSID_MAP[Types::Clsid.load(@ole.root.clsid)]
+ return MIME_TYPES[type] if type
+
+ # fallback to heuristics
+ has_file = Hash[*@ole.root.children.map { |d| [d.name.downcase, true] }.flatten]
+ return MIME_TYPES[:msg] if has_file['__nameid_version1.0'] or has_file['__properties_version1.0']
+ return MIME_TYPES[:doc] if has_file['worddocument'] or has_file['document']
+ return MIME_TYPES[:xls] if has_file['workbook'] or has_file['book']
+
+ MIME_TYPES[nil]
+ end
+
+ def [] key
+ pair = Types::PropertySet::PROPERTY_MAP[key.to_s] or return nil
+ file = FILE_MAP[pair.first] or return nil
+ dirent = @ole.root[file] or return nil
+ dirent.open { |io| return Types::PropertySet.new(io)[key] }
+ end
+
+ def []= key, value
+ raise NotImplementedError, 'meta data writes not implemented'
+ end
+
+ def each(&block)
+ FILE_MAP.values.each do |file|
+ dirent = @ole.root[file] or next
+ dirent.open { |io| Types::PropertySet.new(io).each(&block) }
+ end
+ end
+
+ def to_h
+ inject({}) { |hash, (name, value)| hash.update name.to_sym => value }
+ end
+
+ def method_missing name, *args, &block
+ return super unless args.empty?
+ pair = Types::PropertySet::PROPERTY_MAP[name.to_s] or return super
+ self[name]
+ end
+ end
+
+ def meta_data
+ @meta_data ||= MetaData.new(self)
+ end
+ end
+end
+
diff --git a/vendor/ruby-ole/lib/ole/support.rb b/vendor/ruby-ole/lib/ole/support.rb
new file mode 100644
index 000000000..bbb0bbe68
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/support.rb
@@ -0,0 +1,256 @@
+#
+# A file with general support functions used by most files in the project.
+#
+# These are the only methods added to other classes.
+#
+
+require 'logger'
+require 'stringio'
+require 'enumerator'
+
+class String # :nodoc:
+ # plural of String#index. returns all offsets of +string+. rename to indices?
+ #
+ # note that it doesn't check for overlapping values.
+ def indexes string
+ # in some ways i'm surprised that $~ works properly in this case...
+ to_enum(:scan, /#{Regexp.quote string}/m).map { $~.begin 0 }
+ end
+
+ def each_chunk size
+ (length / size.to_f).ceil.times { |i| yield self[i * size, size] }
+ end
+end
+
+class File # :nodoc:
+ # for interface consistency with StringIO etc (rather than adding #stat
+ # to them). used by RangesIO.
+ def size
+ stat.size
+ end
+end
+
+class Symbol # :nodoc:
+ unless :x.respond_to? :to_proc
+ def to_proc
+ proc { |a| a.send self }
+ end
+ end
+end
+
+module Enumerable # :nodoc:
+ unless [].respond_to? :group_by
+ # 1.9 backport
+ def group_by
+ hash = Hash.new { |h, key| h[key] = [] }
+ each { |item| hash[yield(item)] << item }
+ hash
+ end
+ end
+
+ unless [].respond_to? :sum
+ def sum initial=0
+ inject(initial) { |a, b| a + b }
+ end
+ end
+end
+
+# move to support?
+class IO # :nodoc:
+ # Copy data from IO-like object +src+, to +dst+
+ def self.copy src, dst
+ until src.eof?
+ buf = src.read(4096)
+ dst.write buf
+ end
+ end
+end
+
+class Logger # :nodoc:
+ # A helper method for creating a +Logger+ which produce call stack
+ # in their output
+ def self.new_with_callstack logdev=STDERR
+ log = Logger.new logdev
+ log.level = WARN
+ log.formatter = proc do |severity, time, progname, msg|
+ # find where we were called from, in our code
+ callstack = caller.dup
+ callstack.shift while callstack.first =~ /\/logger\.rb:\d+:in/
+ from = callstack.first.sub(/:in `(.*?)'/, ":\\1")
+ "[%s %s]\n%-7s%s\n" % [time.strftime('%H:%M:%S'), from, severity, msg.to_s]
+ end
+ log
+ end
+end
+
+# Include this module into a class that defines #each_child. It should
+# maybe use #each instead, but its easier to be more specific, and use
+# an alias.
+#
+# I don't want to force the class to cache children (eg where children
+# are loaded on request in pst), because that forces the whole tree to
+# be loaded. So, the methods should only call #each_child once, and
+# breadth first iteration holds its own copy of the children around.
+#
+# Main methods are #recursive, and #to_tree
+module RecursivelyEnumerable # :nodoc:
+ def each_recursive_depth_first(&block)
+ each_child do |child|
+ yield child
+ if child.respond_to? :each_recursive_depth_first
+ child.each_recursive_depth_first(&block)
+ end
+ end
+ end
+
+ # don't think this is actually a proper breadth first recursion. only first
+ # level is breadth first.
+ def each_recursive_breadth_first(&block)
+ children = []
+ each_child do |child|
+ children << child if child.respond_to? :each_recursive_breadth_first
+ yield child
+ end
+ children.each { |child| child.each_recursive_breadth_first(&block) }
+ end
+
+ def each_recursive mode=:depth_first, &block
+ # we always actually yield ourself (the tree root) before recursing
+ yield self
+ send "each_recursive_#{mode}", &block
+ end
+
+ # the idea of this function, is to allow use of regular Enumerable methods
+ # in a recursive fashion. eg:
+ #
+ # # just looks at top level children
+ # root.find { |child| child.some_condition? }
+ # # recurse into all children getting non-folders, breadth first
+ # root.recursive(:breadth_first).select { |child| !child.folder? }
+ # # just get everything
+ # items = root.recursive.to_a
+ #
+ def recursive mode=:depth_first
+ to_enum(:each_recursive, mode)
+ end
+
+ # streams a "tree" form of the recursively enumerable structure to +io+, or
+ # return a string form instead if +io+ is not specified.
+ #
+ # mostly a debugging aid. can specify a different block which will be called
+ # to provide the string form for each node.
+ def to_tree io='', &inspect
+ inspect ||= :inspect.to_proc
+ io << "- #{inspect[self]}\n"
+ recurse = proc do |node, prefix|
+ child = nil
+ node.each_child do |next_child|
+ if child
+ io << "#{prefix}|- #{inspect[child]}\n"
+ recurse.call child, prefix + '| '
+ end
+ child = next_child
+ end if node.respond_to?(:each_child)
+ if child
+ io << "#{prefix}\\- #{inspect[child]}\n"
+ recurse.call child, prefix + ' '
+ end
+ end
+ recurse.call self, ' '
+ io
+ end
+end
+
+# can include File::Constants
+class IO
+ # this is for jruby
+ include File::Constants unless defined?(RDONLY)
+
+ # nabbed from rubinius, and modified
+ def self.parse_mode mode
+ ret = 0
+
+ case mode[0, 1]
+ when 'r'; ret |= RDONLY
+ when 'w'; ret |= WRONLY | CREAT | TRUNC
+ when 'a'; ret |= WRONLY | CREAT | APPEND
+ else raise ArgumentError, "illegal access mode #{mode}"
+ end
+
+ (1...mode.length).each do |i|
+ case mode[i, 1]
+ when '+'; ret = (ret & ~(RDONLY | WRONLY)) | RDWR
+ when 'b'; ret |= Mode::BINARY
+ else raise ArgumentError, "illegal access mode #{mode}"
+ end
+ end
+
+ ret
+ end
+
+ class Mode
+ # ruby 1.9 defines binary as 0, which isn't very helpful.
+ # its 4 in rubinius. no longer using
+ #
+ # BINARY = 0x4 unless defined?(BINARY)
+ #
+ # for that reason, have my own constants module here
+ module Constants
+ include File::Constants
+ BINARY = 0x4
+ end
+
+ include Constants
+ NAMES = %w[rdonly wronly rdwr creat trunc append binary]
+
+ attr_reader :flags
+ def initialize flags
+ flags = IO.parse_mode flags.to_str if flags.respond_to? :to_str
+ raise ArgumentError, "invalid flags - #{flags.inspect}" unless Fixnum === flags
+ @flags = flags
+ end
+
+ def writeable?
+ #(@flags & RDONLY) == 0
+ (@flags & 0x3) != RDONLY
+ end
+
+ def readable?
+ (@flags & WRONLY) == 0
+ end
+
+ def truncate?
+ (@flags & TRUNC) != 0
+ end
+
+ def append?
+ (@flags & APPEND) != 0
+ end
+
+ def create?
+ (@flags & CREAT) != 0
+ end
+
+ def binary?
+ (@flags & BINARY) != 0
+ end
+
+=begin
+ # revisit this
+ def apply io
+ if truncate?
+ io.truncate 0
+ elsif append?
+ io.seek IO::SEEK_END, 0
+ end
+ end
+=end
+
+ def inspect
+ names = NAMES.map { |name| name if (flags & Mode.const_get(name.upcase)) != 0 }
+ names.unshift 'rdonly' if (flags & 0x3) == 0
+ "#<#{self.class} #{names.compact * '|'}>"
+ end
+ end
+end
+
diff --git a/vendor/ruby-ole/lib/ole/types.rb b/vendor/ruby-ole/lib/ole/types.rb
new file mode 100644
index 000000000..95616927a
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/types.rb
@@ -0,0 +1,2 @@
+require 'ole/types/base'
+require 'ole/types/property_set'
diff --git a/vendor/ruby-ole/lib/ole/types/base.rb b/vendor/ruby-ole/lib/ole/types/base.rb
new file mode 100644
index 000000000..31e7b24e9
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/types/base.rb
@@ -0,0 +1,251 @@
+require 'iconv'
+require 'date'
+
+require 'ole/base'
+
+module Ole # :nodoc:
+ #
+ # The Types module contains all the serialization and deserialization code for standard ole
+ # types.
+ #
+ # It also defines all the variant type constants, and symbolic names.
+ #
+ module Types
+ # for anything that we don't have serialization code for
+ class Data < String
+ def self.load str
+ new str
+ end
+
+ def self.dump str
+ str.to_s
+ end
+ end
+
+ class Lpstr < String
+ def self.load str
+ # not sure if its always there, but there is often a trailing
+ # null byte.
+ new str.chomp(0.chr)
+ end
+
+ def self.dump str
+ # do i need to append the null byte?
+ str.to_s
+ end
+ end
+
+ # for VT_LPWSTR
+ class Lpwstr < String
+ FROM_UTF16 = Iconv.new 'utf-8', 'utf-16le'
+ TO_UTF16 = Iconv.new 'utf-16le', 'utf-8'
+
+ def self.load str
+ new FROM_UTF16.iconv(str).chomp(0.chr)
+ end
+
+ def self.dump str
+ # need to append nulls?
+ data = TO_UTF16.iconv str
+ # not sure if this is the recommended way to do it, but I want to treat
+ # the resulting utf16 data as regular bytes, not characters.
+ data.force_encoding Encoding::US_ASCII if data.respond_to? :encoding
+ data
+ end
+ end
+
+ # for VT_FILETIME
+ class FileTime < DateTime
+ SIZE = 8
+ EPOCH = new 1601, 1, 1
+
+ # Create a +DateTime+ object from a struct +FILETIME+
+ # (http://msdn2.microsoft.com/en-us/library/ms724284.aspx).
+ #
+ # Converts +str+ to two 32 bit time values, comprising the high and low 32 bits of
+ # the 100's of nanoseconds since 1st january 1601 (Epoch).
+ def self.load str
+ low, high = str.to_s.unpack 'V2'
+ # we ignore these, without even warning about it
+ return nil if low == 0 and high == 0
+ # switched to rational, and fixed the off by 1 second error i sometimes got.
+ # time = EPOCH + (high * (1 << 32) + low) / 1e7 / 86400 rescue return
+ # use const_get to ensure we can return anything which subclasses this (VT_DATE?)
+ const_get('EPOCH') + Rational(high * (1 << 32) + low, 1e7.to_i * 86400) rescue return
+ # extra sanity check...
+ #unless (1800...2100) === time.year
+ # Log.warn "ignoring unlikely time value #{time.to_s}"
+ # return nil
+ #end
+ #time
+ end
+
+ # +time+ should be able to be either a Time, Date, or DateTime.
+ def self.dump time
+ # i think i'll convert whatever i get to be a datetime, because of
+ # the covered range.
+ return 0.chr * SIZE unless time
+ time = time.send(:to_datetime) if Time === time
+ # don't bother to use const_get here
+ bignum = (time - EPOCH) * 86400 * 1e7.to_i
+ high, low = bignum.divmod 1 << 32
+ [low, high].pack 'V2'
+ end
+
+ def inspect
+ "#<#{self.class} #{to_s}>"
+ end
+ end
+
+ # for VT_CLSID
+ # Unlike most of the other conversions, the Guid's are serialized/deserialized by actually
+ # doing nothing! (eg, _load & _dump are null ops)
+ # Rather, its just a string with a different inspect string, and it includes a
+ # helper method for creating a Guid from that readable form (#format).
+ class Clsid < String
+ SIZE = 16
+ PACK = 'V v v CC C6'
+
+ def self.load str
+ new str.to_s
+ end
+
+ def self.dump guid
+ return 0.chr * SIZE unless guid
+ # allow use of plain strings in place of guids.
+ guid['-'] ? parse(guid) : guid
+ end
+
+ def self.parse str
+ vals = str.scan(/[a-f\d]+/i).map(&:hex)
+ if vals.length == 5
+ # this is pretty ugly
+ vals[3] = ('%04x' % vals[3]).scan(/../).map(&:hex)
+ vals[4] = ('%012x' % vals[4]).scan(/../).map(&:hex)
+ guid = new vals.flatten.pack(PACK)
+ return guid if guid.format.delete('{}') == str.downcase.delete('{}')
+ end
+ raise ArgumentError, 'invalid guid - %p' % str
+ end
+
+ def format
+ "%08x-%04x-%04x-%02x%02x-#{'%02x' * 6}" % unpack(PACK)
+ end
+
+ def inspect
+ "#<#{self.class}:{#{format}}>"
+ end
+ end
+
+ #
+ # The OLE variant types, extracted from
+ # http://www.marin.clara.net/COM/variant_type_definitions.htm.
+ #
+ # A subset is also in WIN32OLE::VARIANT, but its not cross platform (obviously).
+ #
+ # Use like:
+ #
+ # p Ole::Types::Variant::NAMES[0x001f] => 'VT_LPWSTR'
+ # p Ole::Types::VT_DATE # => 7
+ #
+ # The serialization / deserialization functions should be fixed to make it easier
+ # to work with. like
+ #
+ # Ole::Types.from_str(VT_DATE, data) # and
+ # Ole::Types.to_str(VT_DATE, data)
+ #
+ # Or similar, rather than having to do VT_* <=> ad hoc class name etc as it is
+ # currently.
+ #
+ module Variant
+ NAMES = {
+ 0x0000 => 'VT_EMPTY',
+ 0x0001 => 'VT_NULL',
+ 0x0002 => 'VT_I2',
+ 0x0003 => 'VT_I4',
+ 0x0004 => 'VT_R4',
+ 0x0005 => 'VT_R8',
+ 0x0006 => 'VT_CY',
+ 0x0007 => 'VT_DATE',
+ 0x0008 => 'VT_BSTR',
+ 0x0009 => 'VT_DISPATCH',
+ 0x000a => 'VT_ERROR',
+ 0x000b => 'VT_BOOL',
+ 0x000c => 'VT_VARIANT',
+ 0x000d => 'VT_UNKNOWN',
+ 0x000e => 'VT_DECIMAL',
+ 0x0010 => 'VT_I1',
+ 0x0011 => 'VT_UI1',
+ 0x0012 => 'VT_UI2',
+ 0x0013 => 'VT_UI4',
+ 0x0014 => 'VT_I8',
+ 0x0015 => 'VT_UI8',
+ 0x0016 => 'VT_INT',
+ 0x0017 => 'VT_UINT',
+ 0x0018 => 'VT_VOID',
+ 0x0019 => 'VT_HRESULT',
+ 0x001a => 'VT_PTR',
+ 0x001b => 'VT_SAFEARRAY',
+ 0x001c => 'VT_CARRAY',
+ 0x001d => 'VT_USERDEFINED',
+ 0x001e => 'VT_LPSTR',
+ 0x001f => 'VT_LPWSTR',
+ 0x0040 => 'VT_FILETIME',
+ 0x0041 => 'VT_BLOB',
+ 0x0042 => 'VT_STREAM',
+ 0x0043 => 'VT_STORAGE',
+ 0x0044 => 'VT_STREAMED_OBJECT',
+ 0x0045 => 'VT_STORED_OBJECT',
+ 0x0046 => 'VT_BLOB_OBJECT',
+ 0x0047 => 'VT_CF',
+ 0x0048 => 'VT_CLSID',
+ 0x0fff => 'VT_ILLEGALMASKED',
+ 0x0fff => 'VT_TYPEMASK',
+ 0x1000 => 'VT_VECTOR',
+ 0x2000 => 'VT_ARRAY',
+ 0x4000 => 'VT_BYREF',
+ 0x8000 => 'VT_RESERVED',
+ 0xffff => 'VT_ILLEGAL'
+ }
+
+ CLASS_MAP = {
+ # haven't seen one of these. wonder if its same as FILETIME?
+ #'VT_DATE' => ?,
+ 'VT_LPSTR' => Lpstr,
+ 'VT_LPWSTR' => Lpwstr,
+ 'VT_FILETIME' => FileTime,
+ 'VT_CLSID' => Clsid
+ }
+
+ module Constants
+ NAMES.each { |num, name| const_set name, num }
+ end
+
+ def self.load type, str
+ type = NAMES[type] or raise ArgumentError, 'unknown ole type - 0x%04x' % type
+ (CLASS_MAP[type] || Data).load str
+ end
+
+ def self.dump type, variant
+ type = NAMES[type] or raise ArgumentError, 'unknown ole type - 0x%04x' % type
+ (CLASS_MAP[type] || Data).dump variant
+ end
+ end
+
+ include Variant::Constants
+
+ # deprecated aliases, kept mostly for the benefit of ruby-msg, until
+ # i release a new version.
+ def self.load_guid str
+ Variant.load VT_CLSID, str
+ end
+
+ def self.load_time str
+ Variant.load VT_FILETIME, str
+ end
+
+ FROM_UTF16 = Lpwstr::FROM_UTF16
+ TO_UTF16 = Lpwstr::TO_UTF16
+ end
+end
+
diff --git a/vendor/ruby-ole/lib/ole/types/property_set.rb b/vendor/ruby-ole/lib/ole/types/property_set.rb
new file mode 100644
index 000000000..b8d85acba
--- /dev/null
+++ b/vendor/ruby-ole/lib/ole/types/property_set.rb
@@ -0,0 +1,165 @@
+require 'ole/types'
+require 'yaml'
+
+module Ole
+ module Types
+ #
+ # The PropertySet class currently supports readonly access to the properties
+ # serialized in "property set" streams, such as the file "\005SummaryInformation",
+ # in OLE files.
+ #
+ # Think it has its roots in MFC property set serialization.
+ #
+ # See http://poi.apache.org/hpsf/internals.html for details
+ #
+ class PropertySet
+ HEADER_SIZE = 28
+ HEADER_PACK = "vvVa#{Clsid::SIZE}V"
+ OS_MAP = {
+ 0 => :win16,
+ 1 => :mac,
+ 2 => :win32,
+ 0x20001 => :ooffice, # open office on linux...
+ }
+
+ # define a smattering of the property set guids.
+ DATA = YAML.load_file(File.dirname(__FILE__) + '/../../../data/propids.yaml').
+ inject({}) { |hash, (key, value)| hash.update Clsid.parse(key) => value }
+
+ # create an inverted map of names to guid/key pairs
+ PROPERTY_MAP = DATA.inject({}) do |h1, (guid, data)|
+ data[1].inject(h1) { |h2, (id, name)| h2.update name => [guid, id] }
+ end
+
+ module Constants
+ DATA.each { |guid, (name, map)| const_set name, guid }
+ end
+
+ include Constants
+ include Enumerable
+
+ class Section
+ include Variant::Constants
+ include Enumerable
+
+ SIZE = Clsid::SIZE + 4
+ PACK = "a#{Clsid::SIZE}v"
+
+ attr_accessor :guid, :offset
+ attr_reader :length
+
+ def initialize str, property_set
+ @property_set = property_set
+ @guid, @offset = str.unpack PACK
+ self.guid = Clsid.load guid
+ load_header
+ end
+
+ def io
+ @property_set.io
+ end
+
+ def load_header
+ io.seek offset
+ @byte_size, @length = io.read(8).unpack 'V2'
+ end
+
+ def [] key
+ each_raw do |id, property_offset|
+ return read_property(property_offset).last if key == id
+ end
+ nil
+ end
+
+ def []= key, value
+ raise NotImplementedError, 'section writes not yet implemented'
+ end
+
+ def each
+ each_raw do |id, property_offset|
+ yield id, read_property(property_offset).last
+ end
+ end
+
+ private
+
+ def each_raw
+ io.seek offset + 8
+ io.read(length * 8).each_chunk(8) { |str| yield(*str.unpack('V2')) }
+ end
+
+ def read_property property_offset
+ io.seek offset + property_offset
+ type, value = io.read(8).unpack('V2')
+ # is the method of serialization here custom?
+ case type
+ when VT_LPSTR, VT_LPWSTR
+ value = Variant.load type, io.read(value)
+ # ....
+ end
+ [type, value]
+ end
+ end
+
+ attr_reader :io, :signature, :unknown, :os, :guid, :sections
+
+ def initialize io
+ @io = io
+ load_header io.read(HEADER_SIZE)
+ load_section_list io.read(@num_sections * Section::SIZE)
+ # expect no gap between last section and start of data.
+ #Log.warn "gap between section list and property data" unless io.pos == @sections.map(&:offset).min
+ end
+
+ def load_header str
+ @signature, @unknown, @os_id, @guid, @num_sections = str.unpack HEADER_PACK
+ # should i check that unknown == 0? it usually is. so is the guid actually
+ @guid = Clsid.load @guid
+ @os = OS_MAP[@os_id] || Log.warn("unknown operating system id #{@os_id}")
+ end
+
+ def load_section_list str
+ @sections = str.to_enum(:each_chunk, Section::SIZE).map { |s| Section.new s, self }
+ end
+
+ def [] key
+ pair = PROPERTY_MAP[key.to_s] or return nil
+ section = @sections.find { |s| s.guid == pair.first } or return nil
+ section[pair.last]
+ end
+
+ def []= key, value
+ pair = PROPERTY_MAP[key.to_s] or return nil
+ section = @sections.find { |s| s.guid == pair.first } or return nil
+ section[pair.last] = value
+ end
+
+ def method_missing name, *args, &block
+ if name.to_s =~ /(.*)=$/
+ return super unless args.length == 1
+ return super unless PROPERTY_MAP[$1]
+ self[$1] = args.first
+ else
+ return super unless args.length == 0
+ return super unless PROPERTY_MAP[name.to_s]
+ self[name]
+ end
+ end
+
+ def each
+ @sections.each do |section|
+ next unless pair = DATA[section.guid]
+ map = pair.last
+ section.each do |id, value|
+ name = map[id] or next
+ yield name, value
+ end
+ end
+ end
+
+ def to_h
+ inject({}) { |hash, (name, value)| hash.update name.to_sym => value }
+ end
+ end
+ end
+end