Compare commits

...

178 Commits

Author SHA1 Message Date
ksamuel c65d5c4d09
Merge pull request #142 from shashwata27/master
Fixing Typo
2021-03-28 22:00:55 +02:00
Shashwata Saha 3b3cae831a Fixing Typo 2021-03-28 17:06:22 +05:30
ksamuel 3e0c4da193 Removing debug code from dodo file 2020-08-31 17:35:33 +02:00
ksamuel ab10db69b4 Bumping to version 1.0.5 2020-08-31 17:25:50 +02:00
ksamuel ff6fc227eb Fix bug with pyz release on 3.8 2020-08-31 17:25:18 +02:00
ksamuel dff881ee90 Bumping to version 1.0.4 2020-08-31 17:20:07 +02:00
ksamuel 8ce3793ff6 Changing repo status checking strategy, again, again 2020-08-31 16:30:05 +02:00
ksamuel b873215dd8 Changing repo status checking strategy, again 2020-08-31 16:20:59 +02:00
ksamuel 788cf0118d Commiting the new version of compressed css 2020-08-31 16:15:17 +02:00
ksamuel 5dcb0d407e Changing repo status checking strategy 2020-08-31 16:13:57 +02:00
ksamuel 4ab4bffc0f Fix doit API misusage in dodo.py 2020-08-31 16:04:40 +02:00
ksamuel 75d272d430 Automate release to pypi 2020-08-31 15:58:53 +02:00
ksamuel 2725cd10b4 [WIP] automating release 2020-08-31 14:26:14 +02:00
papee b83f284b98 Merge branch 'v2' of github.com:Tygs/0bin into v2 2020-08-26 12:58:14 +02:00
papee 90672f7b39 change meta description 2020-08-26 12:58:10 +02:00
ksamuel 536fa12975 Merge ok 2020-08-25 20:19:32 +02:00
ksamuel b86bc2c0ce Bump version 2020-08-25 20:18:42 +02:00
ksamuel 73ce7c4e14 Merge branch 'v2' 2020-08-25 20:12:30 +02:00
ksamuel a29a7cb961 Fix typos in buy bitcoin page 2020-08-25 20:12:16 +02:00
ksamuel a9b2f53e65 Fix inline code block in README 2020-08-19 12:41:39 +02:00
ksamuel 6c64f28643 Update README to demonstrate pyz 2020-08-19 12:38:50 +02:00
ksamuel 5b515f4c35 Bump version 2020-08-19 11:59:04 +02:00
ksamuel 9558751f5a Merge branch 'v2' 2020-08-19 11:58:19 +02:00
ksamuel aa064a5724 Add entry point, remove setup.py, add pyproject.toml, better default_settings 2020-08-19 11:58:10 +02:00
papee ed12243d67 Merge branch 'v2' of github.com:Tygs/0bin into v2 2020-08-18 15:00:44 +02:00
papee 7364cd2020 buy bitcoin guide 2020-08-18 15:00:38 +02:00
ksamuel 183150a6b0 Release v1.0.1 2020-08-18 14:41:46 +02:00
ksamuel 0b1fad5a2b Merge branch 'v2' 2020-08-18 14:38:46 +02:00
ksamuel fab8f31064 Change compress tooling to avoid svg tag scrambling 2020-08-18 14:38:31 +02:00
ksamuel eb5ecdb04e Merge branch 'v2' of ksamuel-github:/Tygs/0bin into v2 2020-08-18 13:26:33 +02:00
ksamuel cf3a05ab53 Migrate build system to pydoit 2020-08-18 13:26:29 +02:00
papee 2bba894d67 buy bitcoin guide 2020-08-18 09:17:58 +02:00
ksamuel ea3ee454b6 Merge branch 'v2' 2020-08-15 16:30:26 +02:00
papee b54574d5ca binance link to buy btc responsive 2020-08-15 16:21:25 +02:00
papee ff00c972b1 Merge branch 'v2' of github.com:Tygs/0bin into v2 2020-08-15 16:17:29 +02:00
papee d753f92207 binance link to buy btc responsive 2020-08-15 16:17:18 +02:00
ksamuel 6951d3da56 Fix README rst typo 2020-08-15 14:47:17 +02:00
ksamuel b73ce9684f Tweak command to remove expired pastes 2020-08-15 14:46:06 +02:00
ksamuel 262db62265 Add command to remove expired pastes 2020-08-15 14:40:26 +02:00
ksamuel 32ab32aead Reader for prod 2020-08-15 14:17:35 +02:00
papee 0f780ddabc binance link to buy btc responsive 2020-08-15 14:13:00 +02:00
papee 93db217998 binance link to buy btc 2020-08-15 14:06:19 +02:00
papee 8dfbf91e38 Merge branch 'v2' of github.com:Tygs/0bin into v2 2020-08-15 13:27:59 +02:00
papee c9f1d22054 center previous pastes 2020-08-15 13:27:51 +02:00
ksamuel 285da1c596 Edit README and setup.cfg for prod 2020-08-15 13:24:46 +02:00
papee 784b07de14 previous paste css firefox 2020-08-15 12:56:05 +02:00
papee 0c18aae662 Merge branch 'v2' of github.com:Tygs/0bin into v2 2020-08-15 12:50:22 +02:00
papee d851f8c74c base template 2020-08-15 12:50:07 +02:00
ksamuel 0e936db771 Augment compression quality for images 2020-08-15 12:08:04 +02:00
ksamuel c4e3718c01 Merge branch 'v2' of ksamuel-github:/Tygs/0bin into v2 2020-08-15 12:07:47 +02:00
ksamuel 6d00fd5972 Compress image prior to uploading 2020-08-15 12:05:22 +02:00
papee 0d824b70bd Merge branch 'v2' of github.com:Tygs/0bin into v2 2020-08-15 11:53:36 +02:00
papee c95fe5a2f7 css responsive icons menu 2020-08-15 11:53:32 +02:00
ksamuel 233b66fa68 Regenerate prod files 2020-08-15 11:09:34 +02:00
ksamuel d953e28e23 Fix delete error message and missing button 2020-08-15 10:54:55 +02:00
papee d11081fc54 merge 2020-08-15 10:33:19 +02:00
papee 762c18eb74 favicon & css img 2020-08-15 10:29:22 +02:00
ksamuel 9caaaf51f7 Fix link to close alert messages 2020-08-15 10:27:52 +02:00
ksamuel 67433e4a17 Remove upload artefact on second attempt 2020-08-15 10:18:44 +02:00
ksamuel eb22cd90b2 Add copy btc to clipboard 2020-08-15 10:02:57 +02:00
papee 3a40ea6694 merge 2020-08-15 09:21:56 +02:00
papee 23133e362c css btc tip 2020-08-14 18:20:31 +02:00
ksamuel 183f9c978b Remove additional.min.js 2020-08-14 18:19:43 +02:00
ksamuel 855c26332a Update bundling machinery 2020-08-14 18:18:17 +02:00
ksamuel d6d987f476 Update README 2020-08-14 18:15:45 +02:00
papee 862cc3bdfa css paste title 2020-08-14 17:10:35 +02:00
ksamuel 701fed35b2 Fix WSGI prod file 2020-08-14 16:54:57 +02:00
ksamuel b9831c7f60 In dev mode, display the full admin URL at server start 2020-08-14 16:45:06 +02:00
ksamuel 2d9dab6591 Merge branch 'v2' of ksamuel-github:/Tygs/0bin into v2 2020-08-14 16:41:55 +02:00
ksamuel 952d77a830 Add btc tip 2020-08-14 16:41:45 +02:00
papee 6d98c4e6a8 merge 2020-08-14 16:03:51 +02:00
papee 052fc4167a admin css design 2020-08-14 16:02:05 +02:00
ksamuel 65f5578a6c Merge branch 'v2' of ksamuel-github:/Tygs/0bin into v2 2020-08-14 15:09:04 +02:00
ksamuel d9a79f46dc Add paste title 2020-08-14 15:08:55 +02:00
papee 2b5dcc9ba9 Merge branch 'v2' of github.com:Tygs/0bin into v2 2020-08-14 11:32:19 +02:00
papee 89d25030d4 cleaning reader mode and admin css 2020-08-14 11:32:14 +02:00
ksamuel 6bf5333b14 Reader mode param in URL 2020-08-14 11:18:59 +02:00
ksamuel d133494e9d Fix upload button glitch 2020-08-14 10:26:04 +02:00
ksamuel 9b2f9911f2 Add batch paste methods and fix dl link glitch 2020-08-14 10:07:36 +02:00
ksamuel e37ed06a47 Add dl for text 2020-08-13 19:04:57 +02:00
ksamuel e667e1f947 Add active class on previousPasteMenu 2020-08-13 18:23:11 +02:00
ksamuel 2abbab5305 Handle paste 2020-08-13 18:14:19 +02:00
ksamuel 1f35f96fe3 Add padding to paste 2020-08-13 17:26:25 +02:00
ksamuel d0cca8cdd9 Merge ok 2020-08-13 17:18:59 +02:00
ksamuel cc3ed69710 Replace pre reader mode by a div 2020-08-13 17:17:46 +02:00
papee 8b72a90a32 merge 2020-08-13 17:01:45 +02:00
papee 128cd64adf css pre for read mode 2020-08-13 16:54:23 +02:00
ksamuel cda601a355 Add toggle for reader mode 2020-08-13 16:53:17 +02:00
papee 1939a6b89e merge 2020-08-13 16:23:38 +02:00
ksamuel 8f7ed5cddc Small fix on settings style and requirements 2020-08-13 15:54:50 +02:00
papee 836fa17bc4 Merge branch 'v2' of github.com:Tygs/0bin into v2 2020-08-13 15:25:49 +02:00
papee ad016e50bd css & fix image display 2020-08-13 15:25:19 +02:00
ksamuel ba14ae969e Sticky footer 2020-08-13 14:54:34 +02:00
ksamuel 6c1dad2626 Put config app dir 2020-08-13 14:36:42 +02:00
ksamuel 2d3f3998de Fix favicon 404 in dev 2020-08-12 17:34:19 +02:00
ksamuel 64e3a285f9 Remove privilege lib 2020-08-12 17:30:39 +02:00
ksamuel 3308e6d74f Move from cherrypy to paste 2020-08-12 17:26:21 +02:00
ksamuel aaf8e647d6 Add licence file 2020-08-12 16:52:47 +02:00
ksamuel dc6514a11f Remove expiration 2020-08-12 16:10:37 +02:00
ksamuel 92251432bd Fix notifications and 404 history cleaning 2020-08-12 16:08:19 +02:00
ksamuel e5faaac077 Delete paste in admin 2020-08-12 15:39:07 +02:00
papee b6732d7348 404 theme css 2020-08-12 15:27:46 +02:00
papee 425a947051 404 css jolifygationnement merge 2020-08-12 15:23:42 +02:00
papee 0c87b2339f 404 css jolifygationnement 2020-08-12 15:22:47 +02:00
ksamuel 3b4c57e850 Add empty admin page with login 2020-08-12 15:21:49 +02:00
ksamuel 500ca82b69 Merge branch 'v2' of ksamuel-github:/Tygs/0bin into v2 2020-08-12 14:32:29 +02:00
papee eb05aaa103 email on bottom site + footer menu 2020-08-12 14:28:58 +02:00
papee 130bedf6ec email on bottom site + footer menu 2020-08-12 14:25:22 +02:00
ksamuel 1007faa8f5 Merge branch 'admin' into v2 2020-08-12 14:14:39 +02:00
ksamuel 65578a4c72 Merge branch 'v2' of ksamuel-github:/Tygs/0bin into v2 2020-08-12 14:14:28 +02:00
ksamuel 6a0370b1f5 Autofit textarea 2020-08-12 14:14:13 +02:00
ksamuel 7bf0ba4ddd Remove image copy to clipboard 2020-08-12 14:02:13 +02:00
papee c34f9f77e7 merge new prettyffy 2020-08-12 11:41:02 +02:00
papee e5637fd7bf change prettify code error in behavior 2020-08-12 11:35:35 +02:00
papee a057e31e5d change prettify code error in behavior 2020-08-12 11:34:33 +02:00
ksamuel 2aeaa0c482 Fix email this 2020-08-12 11:21:18 +02:00
ksamuel b2f49953f5 Fix progress bar 2020-08-12 11:15:01 +02:00
ksamuel 23d7b73514 Fix 404 2020-08-12 11:01:31 +02:00
ksamuel 7c6adb32ab Fix previousPaste menu 2020-08-12 11:00:41 +02:00
ksamuel 601aa5a16b Post merge fixes 2020-08-12 10:19:31 +02:00
ksamuel 933118c1cd Merge new theme 2020-08-12 10:11:51 +02:00
papee 2e67a09bca Merge branch 'v2' of github.com:Tygs/0bin into v2 2020-08-12 09:34:14 +02:00
jhon doe a8e6763cc0 new theme 2020-08-12 09:26:17 +02:00
ksamuel 88df4787bf WIP: add admin views 2020-08-12 09:19:38 +02:00
ksamuel 6843a19fa8 Add delete paste 2020-08-11 16:37:03 +02:00
ksamuel e13cead89b jQuery fully removed, clipboard fixed 2020-08-11 11:55:29 +02:00
ksamuel 722fb45b51 Python 3 support only. Remove vendored libs. 2020-08-10 10:48:47 +02:00
sametmax 29dc683287
Merge pull request #127 from nekokatt/patch-1
Fixed grammatical error in metadata tag
2019-11-18 07:54:59 +01:00
nekokatt bd9b5d4d25
Fixed grammatical error in metadata tag 2019-11-16 17:32:23 +00:00
sametmax 14a00ece3c
Merge pull request #121 from KardiffInker/patch-1
Updated Spelling
2019-05-23 10:04:55 +02:00
KardiffInker fbbe23c959
Updated Spelling 2019-05-20 10:31:21 +01:00
sametmax 7da1615d41 Merge pull request #91 from Shir0kamii/master
Fix burn_after_reading feature, along with zerobin.py
2016-01-08 14:10:38 +01:00
Shir0kamii 8c1379b827 Fix burn_after_reading feature, along with zerobin.py 2015-10-04 19:47:00 +02:00
sametmax a344dbb18f Merge pull request #88 from Shir0kamii/bin_remove_paste
Bin remove paste
2015-09-18 19:01:49 +02:00
Shir0kamii 590ea9913f Merge remove_paste.py and and zerobin.py in cmd.py
The two commands have been merged together, using subcommands.

`remove_paste` is now accessible with `zerobin delete-paste`

`zerobin` is moved to `zerobin runserver` but is still accessible
with `zerobin` to keep compatibility with older versions. Unfortunately,
clize is not structured to allow default subcommand and the actual
implementation is nothing but a hack.
2015-09-18 17:36:14 +02:00
Shir0kamii bd7a372551 Add compatibility with python 2 2015-09-17 02:51:31 +02:00
Shir0kamii 7aa7873b48 Fixes according to comments by @RaitoBezarius 2015-09-14 21:38:27 +02:00
Shir0kamii c33de9c0dc Add a script to remove paste, either from URL of from ID 2015-09-10 23:14:18 +02:00
sametmax 11ff63d931 Merge pull request #86 from Shir0kamii/fix_faq
Remove TrueCrypt from the FAQ
2015-09-07 16:12:46 +02:00
sametmax 7159d8b12f Merge pull request #85 from Shir0kamii/feature_lockfile
Use lockfile library instead of custom implementation
2015-09-06 07:57:00 +02:00
Shir0kamii 85c0395c37 Use lockfile library instead of custom implementation 2015-09-03 18:45:42 +02:00
Shir0kamii d47aa95362 Remove TrueCrypt from the FAQ 2015-09-03 18:42:20 +02:00
sametmax 66fe5d4088 Merge pull request #75 from meowstars/master
Add https support for cli tool
2015-07-08 23:14:10 +02:00
sametmax b87a0db3a4 Bumped python version in doc 2015-05-10 19:53:55 +02:00
sametmax bfde901a46 Merge branch 'master' of sametmax-github:/sametmax/0bin 2015-05-10 19:49:30 +02:00
sametmax fd55001894 Bump version 2015-05-10 19:47:59 +02:00
sametmax 3a6e8a48fe Fixing the 'Mail this' feature 2015-05-10 19:46:30 +02:00
sametmax 9b84122414 Porting zerobin to python 3 2015-05-10 19:19:02 +02:00
Yann Bordenave f193dd9403 Add https support for cli tool 2015-04-28 17:59:42 +02:00
sametmax e5586121d1 Merge pull request #72 from Mkaysi/patch-1
README.rst: be really gender neutral
2014-10-23 08:25:02 +02:00
Mikaela Suomalainen 41d9c3e47b README.rst: be really gender neutral 2014-10-19 10:34:44 +03:00
sametmax 391df055f9 Settings now don't fiddle with sys.path 2014-06-22 12:42:21 +07:00
sametmax 1a0426881a Made 0.4 release easily installable
Static files are compressed
Mention of compress.sh in README
Privilege is now an embeded dependancy.
2014-06-22 12:02:17 +07:00
sametmax eda8c4ae22 Bumping version, adding editor config file and a contribution section to the readme. 2014-06-22 11:00:05 +07:00
sametmax c13dc59629 Merge pull request #67 from Natim/set-default-key-size
Set the default key size to 256 bits.
2014-06-20 15:00:43 +07:00
Rémy HUBSCHER ff0e9f13f6 Set the default key size to 256 bits. 2014-06-20 09:16:17 +02:00
sametmax 6387eb170a Merge pull request #55 from iceTwy/master
Rewrite & add further info to the FAQ
2013-10-03 16:05:42 -07:00
iceTwy 706ad42098 Rewrite & add further info to the FAQ
The FAQ contained a bunch of typos and inaccurate information. Hopefully, this should fix both problems.
2013-10-03 23:36:30 +02:00
sametmax 1a99bde81c Update easy_install.rst
Removed Python 2.6 support from FR doc.
2013-10-02 10:42:16 +02:00
sametmax 255d9ba0f7 Update easy_install.rst
Removed Python 2.6 support from EN doc.
2013-10-02 10:41:57 +02:00
sametmax 80734eb622 Update README.rst
Added supported Python version
2013-10-02 10:37:36 +02:00
sametmax bb9b84cbfc Merge pull request #53 from xaocon/master
Fixed a link in README.rst
2013-10-01 07:18:51 -07:00
Evan b68b327880 Fixed a link in README.rst
The cherrypy link included comment information so it was broken.
2013-09-27 15:03:29 -04:00
sametmax 522bc27f1c Moved deployment scripts in a dedicated directory. Added an importable wsgi.py module (app.wsgi works only for Apache, and now imports wsgi.py) and a script for gunicorn. Settings file can now be specified using an environnement variable. 2013-07-03 11:12:12 +02:00
sametmax 9e48029ef9 Improved setup / build 2013-06-23 16:12:47 +02:00
sametmax 04099c631a Merge pull request #41 from mk-fg/zerobinpaste_updates
Zerobinpaste updates: seed prng from /dev/(u)random, shorter keys
2013-05-04 05:01:00 -07:00
sametmax 759bb82d93 Merge pull request #42 from mk-fg/shorter_urls
Shorter urls. Thanks again for this piece of work to the author.
2013-05-04 04:58:01 -07:00
Mike Kazantsev d82669e8ee Generate shorter 48-bit keys in the interface js 2013-05-01 22:40:06 +06:00
Mike Kazantsev 6675209e31 Use base64-like (dashes in place of slashes) paste-ids with (configurable) 8-char length limit 2013-05-01 22:40:05 +06:00
Mike Kazantsev 118b0ce47d zerobinpaste: use 48b keys (only 6 chars) by default 2013-04-30 00:28:24 +06:00
Mike Kazantsev 415516ee0c zerobinpaste: seed sjcl prng from /dev/(u)random 2013-04-30 00:28:02 +06:00
sametmax 353fac415a Renerated the HTML documentation 2013-04-22 23:22:33 +02:00
sametmax 33d94fdbbd Updated doc menu to include new file 2013-04-22 23:17:28 +02:00
sametmax 18e407b152 Translated last documentation addition to french 2013-04-22 23:15:24 +02:00
sametmax 09d7bb4f24 Merge pull request #39 from mk-fg/add_zerobinpaste_cli_tool
Add simple "zerobinpaste" cli tool
2013-04-22 13:30:56 -07:00
Mike Kazantsev dabae7a130 Update docs wrt zerobinpaste tool 2013-04-23 01:48:48 +06:00
Mike Kazantsev 4eb40b9c8f zerobinpaste: add --expire option 2013-04-23 00:39:29 +06:00
Mike Kazantsev ce7ede3e35 Added simple "zerobinpaste" cli tool 2013-04-20 23:39:26 +06:00
190 changed files with 29042 additions and 31692 deletions

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.py]
indent_size = 4
[*.{html,js,rb,scss,xml,less,css}]
indent_size = 2

30
.gitignore vendored
View File

@ -7,25 +7,41 @@
*_index
*.orig
*.swp
.* # all hidden files...
!.gitignore # ...except gitignore
*.old
# binaries
*.pyc
*.pyo
*.pyz
*.zip
__pycache__
.mypy*
*.db
# files generated by setuptools
# build and env
build
0bin
env
venv
*.egg-info
dist
*.tar.gz
*.egg
*.in
# others
content
# IDE
*.sublime-project
*.sublime-workspace
settings_local.py
*-workspace
*_old*
.vscode
# JS
package-lock.json
node_modules

597
.pylintrc Normal file
View File

@ -0,0 +1,597 @@
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=
# Specify a score threshold to be exceeded before program exits with error.
fail-under=10
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=print-statement,
parameter-unpacking,
unpacking-in-except,
old-raise-syntax,
backtick,
long-suffix,
old-ne-operator,
old-octal-literal,
import-star-module-level,
non-ascii-bytes-literal,
raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
apply-builtin,
basestring-builtin,
buffer-builtin,
cmp-builtin,
coerce-builtin,
execfile-builtin,
file-builtin,
long-builtin,
raw_input-builtin,
reduce-builtin,
standarderror-builtin,
unicode-builtin,
xrange-builtin,
coerce-method,
delslice-method,
getslice-method,
setslice-method,
no-absolute-import,
old-division,
dict-iter-method,
dict-view-method,
next-method-called,
metaclass-assignment,
indexing-exception,
raising-string,
reload-builtin,
oct-method,
hex-method,
nonzero-method,
cmp-method,
input-builtin,
round-builtin,
intern-builtin,
unichr-builtin,
map-builtin-not-iterating,
zip-builtin-not-iterating,
range-builtin-not-iterating,
filter-builtin-not-iterating,
using-cmp-argument,
eq-without-hash,
div-method,
idiv-method,
rdiv-method,
exception-message-attribute,
invalid-str-codec,
sys-max-int,
bad-python3-import,
deprecated-string-function,
deprecated-str-translate-call,
deprecated-itertools-function,
deprecated-types-field,
next-method-defined,
dict-items-not-iterating,
dict-keys-not-iterating,
dict-values-not-iterating,
deprecated-operator-function,
deprecated-urllib-function,
xreadlines-attribute,
deprecated-sys-function,
exception-escape,
comprehension-escape,
invalid-name,
attribute-defined-outside-init
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[REPORTS]
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'error', 'warning', 'refactor', and 'convention'
# which contain the number of messages in each category, as well as 'statement'
# which is the total number of statements analyzed. This score is used by the
# global evaluation report (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
#msg-template=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local,SettingsContainer
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# List of decorators that change the signature of a decorated function.
signature-mutators=
[LOGGING]
# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[SIMILARITIES]
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
# Minimum lines number of a similarity.
min-similarity-lines=4
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,
dict-separator
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
# Regular expression of note tags to take in consideration.
#notes-rgx=
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style.
#variable-rgx=
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. Available dictionaries: none. To make it work,
# install the python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp,
__post_init__
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[DESIGN]
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled).
ext-import-graph=
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled).
import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions=BaseException,
Exception

14
LICENCE Normal file
View File

@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

View File

@ -1,26 +1,29 @@
0bin
====
0bin: a client side encrypted pastebin
===========================================
0bin is a client side encrypted pastebin that can run without a database.
0bin is a pastebin that encrypts the user content in the browser and can run without a database.
* Try it: `0bin.net <http://0bin.net>`_
* `Report a bug <https://github.com/sametmax/0bin/issues>`_
0bin allows anybody to host a pastebin while welcoming any type of content to
be pasted in it. The idea is that one can (probably...) not be legally entitled
to `moderate the pastebin content`_ as he/she has no way to decrypt it.
to `moderate the pastebin content`_ as they have no way to decrypt it.
It's an Python implementation of the
`zerobin project`_ under the `WTF licence`_. It's easy to
install even if you know nothing about Python.
It's an Python implementation of the `zerobin project`_, created by sebsauvage, under the `WTFPL licence`_.
For now tested with IE9, and the last opera, safari, chrome and FF.
To run zerobin, download zerobin.pyz from the latest release_ then:
There is a `good doc <http://readthedocs.org/docs/0bin/en/latest/>`_,
but in short::
::
python zerobin.pyz
0bin requires Python 3.7 or higher.
You may need to type :code:`py -3.7 zerobin.pyz` on Windows, or :code:`python3.7 zerobin.pyz` on Mac/Linux, depending on your configuration.
If you are familiar with the Python ecosystem, you can also :code:`python -m pip install zerobin --user` and run :code:`python -m zerobin` for the same effect.
pip install zerobin
zerobin
How it works
=============
@ -49,52 +52,30 @@ Key points:
Other features
======================
- automatic code coloration (no need to specify);
- automatic code coloration (no need to specify the language);
- pastebin expiration: 1 day, 1 month or never;
- burn after reading: the paste is destroyed after the first reading;
- clone paste: you can't edit a paste, but you can duplicate any of them;
- code upload: if a file is too big, you can upload it instead of using copy/paste;
- copy paste to clipboard in a click;
- get paste short URL in a click;
- own previous pastes history;
- visual hash of a paste to easily tell it apart from others in a list.
Technologies used
==================
- Python_
- `The Bottle Python Web microframework`_
- SJCL_ (js crypto tools)
- jQuery_
- Bootstrap_, the Twitter HTML5/CSS3 framework
- VizHash.js_ to create visual hashes from pastes
- Cherrypy_ (server only)
- reader mode;
Known issues
============
- 0bin uses several HTML5/CSS3 features that are not widely supported. In that case we handle the degradation as gracefully as we can.
- The "copy to clipboard" feature is buggy under linux. It's flash, so we won't fix it. Better wait for the HTML5 clipboard API to be implemented in major browsers.
- The pasted content size limit check is not accurate. It's just a safety net, so we think it's ok.
- Some url shorteners and other services storing URLs break the encryption key. We will sanitize the URL as much as we can, but there is a limit to what we can do.
What does 0bin not implement?
=================================
* Request throttling. It would be inefficient to do it at the app level, and web servers have robust implementations for it.
* Hash collision prevention: the ratio "probability it happens/consequence seriousness" `is not worth it`_
* Comments: it was initially planed. But comes with a lot of issues so we chose to focus on lower hanging fruits.
.. _moderate the pastebin content: http://www.zdnet.com/blog/security/pastebin-to-hunt-for-hacker-pastes-anonymous-cries-censorship/11336
.. _zerobin project: https://github.com/sebsauvage/ZeroBin/
.. _Python: https://en.wikipedia.org/wiki/Python_(programming_language)
.. _The Bottle Python Web microframework: http://bottlepy.org/
.. _SJCL: http://crypto.stanford.edu/sjcl/
.. _jQuery: http://jquery.com/
.. _Bootstrap: http://twitter.github.com/bootstrap/
.. _VizHash.js: https://github.com/sametmax/VizHash.js
.. _Cherrypy: http://www.cherrypy.org/ (server only)
.. _node.js: http://nodejs.org/
.. _is not worth it: http://stackoverflow.com/questions/201705/how-many-random-elements-before-md5-produces-collisions
.. _WTF licence: http://en.wikipedia.org/wiki/WTFPL
.. _WTFPL licence: http://en.wikipedia.org/wiki/WTFPL
.. _release: https://github.com/Tygs/0bin/releases
Contributing
=============
We cannot accept contributions for the moment, and will ignore PR.

View File

@ -1,51 +1,56 @@
#! /bin/bash
COMMAND="yui-compressor";
command -v $COMMAND >/dev/null 2>&1 || { echo >&2 "Error: this script requires the command '$COMMAND' to be available"; exit 1; }
export PYTHONWARNINGS=ignore::FutureWarning # scss prints future warnings
CURDIR=$(dirname $(readlink -f $0));
STATICDIR=$CURDIR'/zerobin/static/'
CSSDIR=$STATICDIR'css/'
JSDIR=$STATICDIR'js/'
python -c "import scss" || {
echo >&2 "Error: this script requires the scss python module. pip install -r dev-requirements.txt"
exit 1
}
MAIN_JS_OUTPUT=$JSDIR"main.min.js";
ADDITIONAL_JS_OUTPUT=$JSDIR"additional.min.js";
command -v "uglifyjs" >/dev/null 2>&1 || {
echo >&2 "Error: this script requires the command 'uglifyjs' to be available."
exit 1
}
CURDIR=$(dirname $(readlink -f $0))
CSSDIR=$CURDIR'/zerobin/static/css/'
JSDIR=$CURDIR'/zerobin/static/js/'
MAIN_JS_OUTPUT=$JSDIR"main.min.js"
CSS_OUTPUT=$CSSDIR"style.min.css"
cat /dev/null > $CSS_OUTPUT;
cat /dev/null >$CSS_OUTPUT
echo "Compressing CSS..."
echo $'\n''/* Bootstrap */' >> $CSS_OUTPUT;
$COMMAND $CSSDIR'bootstrap.min.css' >> $CSS_OUTPUT;
echo $'\n''/* Prettify */' >> $CSS_OUTPUT;
cat $CSSDIR'prettify.css' >> $CSS_OUTPUT;
echo $'\n''/* Custom */' >> $CSS_OUTPUT;
$COMMAND $CSSDIR'style.css' >> $CSS_OUTPUT;
echo $'\n''/* Prettify */' >>$CSS_OUTPUT
python -m scss $CSSDIR'prettify.css' >>$CSS_OUTPUT
echo $'\n''/* Desert prettify theme */' >>$CSS_OUTPUT
python -m scss $CSSDIR'desert.css' >>$CSS_OUTPUT
echo $'\n''/* Bootswatch bootstrap theme */' >>$CSS_OUTPUT
python -m scss $CSSDIR'bootswatch.4.5.css' >>$CSS_OUTPUT
echo $'\n''/* Our own CSS */' >>$CSS_OUTPUT
python -m scss $CSSDIR'style.css' >>$CSS_OUTPUT
echo "Compressing JS..."
cat /dev/null > $MAIN_JS_OUTPUT;
cat /dev/null >$MAIN_JS_OUTPUT
echo $'\n''/* Vue */' >>$MAIN_JS_OUTPUT
uglifyjs $JSDIR'vue.js' >>$MAIN_JS_OUTPUT
echo $'\n''/* jQuery */' >> $MAIN_JS_OUTPUT;
cat $JSDIR'jquery-1.7.2.min.js' >> $MAIN_JS_OUTPUT;
# strip the "use strict" statement because it will apply to all the files
# TODO: file a bug report to SJCL to invite them to use the function syntax
echo $'\n''/* SJCL */' >> $MAIN_JS_OUTPUT;
cat $JSDIR'sjcl.js' | sed 's/"use strict";//' >> $MAIN_JS_OUTPUT;
echo $'\n''/* custom */' >> $MAIN_JS_OUTPUT;
$COMMAND $JSDIR'behavior.js' >> $MAIN_JS_OUTPUT;
echo $'\n''/* SJCL */' >>$MAIN_JS_OUTPUT
uglifyjs $JSDIR'sjcl.js' | sed 's/"use strict";//' >>$MAIN_JS_OUTPUT
cat /dev/null > $ADDITIONAL_JS_OUTPUT;
echo $'\n''/* Our own JS */' >>$MAIN_JS_OUTPUT
uglifyjs $JSDIR'behavior.js' >>$MAIN_JS_OUTPUT
echo $'\n''/* jQuery Elastic */' >> $ADDITIONAL_JS_OUTPUT;
$COMMAND $JSDIR'jquery.elastic.source.js' >> $ADDITIONAL_JS_OUTPUT;
echo $'\n''/* lzw */' >> $ADDITIONAL_JS_OUTPUT;
$COMMAND $JSDIR'lzw.js' >> $ADDITIONAL_JS_OUTPUT;
echo $'\n''/* prettify */' >> $ADDITIONAL_JS_OUTPUT;
cat $JSDIR'prettify.min.js' >> $ADDITIONAL_JS_OUTPUT;
echo $'\n''/* ZeroClipboard */' >> $ADDITIONAL_JS_OUTPUT;
$COMMAND $JSDIR'ZeroClipboard.js' >> $ADDITIONAL_JS_OUTPUT;
echo $'\n''/* Prettify */' >>$MAIN_JS_OUTPUT
uglifyjs $JSDIR'prettify.min.js' >>$MAIN_JS_OUTPUT
echo "Done"

3
dev-requirements.txt Normal file
View File

@ -0,0 +1,3 @@
doit==0.32.0
pyScss==1.3.7
twine==3.2.0

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: da73410994192a33f4631d2ef9f7f005
tags: fbb0d17656682115ca4d033fb2f83ba1
config: b6ae59e2f6e05e37e1e019164ebb6189
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

@ -16,7 +16,7 @@ An Apache setup is still much more robust and secure than an easy installation.
Une installation apache est aussi beaucoup plus solide et sécurisé qu'une
installation facile. You'll benefit from having:
- the possiblity to have several projects listening to the port 80;
- the possibility to have several projects listening to the port 80;
- several Apache module at your disposal (like requests throttling);
- Apache robustness in front end: it's secure, and there is much less chance
it will crash under heavy load;
@ -97,4 +97,4 @@ CGI
===
You can also run 0bin using CGI, but infortunaly we didn't have time to cover
it yet. Please contact us if you ever get the need to use it.
it yet. Please contact us if you ever get the need to use it.

View File

@ -17,8 +17,8 @@ Just type::
30 seconds setup (for anybody)
===============================
- Make sure you have Python 2.6 or 2.7 (`python --version`)
- Download the last zip of the sources code.
- Make sure you have Python 2.7 or 3.4+ (`python --version`)
- Download the last `zip of the source code <https://github.com/sametmax/0bin/zipball/master>`_
- Extract all of it where you wish the site to be stored.
- Go to the extracted files.
- Run with the admin rights::
@ -59,4 +59,4 @@ Or run it in a screen.
0bin will now be accessible from http://your.local.ip.address:8000.
This can be very cool way to share code in a companie or during a code sprint.
This can be very cool way to share code in a companie or during a code sprint.

View File

@ -45,7 +45,8 @@ Other features
- copy paste to clipboard in a click;
- get paste short URL in a click;
- own previous pastes history;
- visual hash of a paste to easily tell it appart from others in a list.
- visual hash of a paste to easily tell it apart from others in a list;
- `optional command-line tool`_ to encrypt and paste data from shell or scripts.
Technologies used
==================
@ -57,6 +58,7 @@ Technologies used
- Bootstrap_, the Twitter HTML5/CSS3 framework
- VizHash.js_ to create visual hashes from pastes
- Cherrypy_ (server only)
- `node.js`_ (for optional command-line tool only)
Known issues
@ -84,4 +86,6 @@ What does 0bin not implement?
.. _Bootstrap: http://twitter.github.com/bootstrap/
.. _VizHash.js: https://github.com/sametmax/VizHash.js
.. _Cherrypy: http://www.cherrypy.org/ (server only)
.. _is not worth it: http://stackoverflow.com/questions/201705/how-many-random-elements-before-md5-produces-collisions
.. _node.js: http://nodejs.org/
.. _optional command-line tool: ./zerobinpaste_tool
.. _is not worth it: http://stackoverflow.com/questions/201705/how-many-random-elements-before-md5-produces-collisions

View File

@ -24,7 +24,7 @@ Therefor there are two steps:
You will benefit from having:
- the possiblity to have several projects listening to the port 80;
- the possibility to have several projects listening to the port 80;
- several Apache module at your disposal (like requests throttling);
- Apache robustness in front end: it's secure, and there is much less chance
it will crash under heavy load;
@ -40,7 +40,7 @@ Run 0bin as usual, but this time make it listen to a local port and host. E.G::
zerobin --host 127.0.0.1 --port 8000
In PHP, when you edit a file, the changes are immediatly visible. In Python,
In PHP, when you edit a file, the changes are immediately visible. In Python,
the whole code is often loaded in memory for performance reasons. This means
you have to restart the Python process to see the changes effect. Having a
separate process let you do this without having to restart the server.

View File

@ -159,7 +159,7 @@ List of absolute path to directories containing templates that 0bin uses to
generate the web site pages. The first list items have priotity on the later.
If you wish to use your own templates, add the directory containing them
at the begining of the list::
at the beginning of the list::
from zerobin.defauls_settings import TEMPLATE_DIRS
@ -225,4 +225,4 @@ Valeur approximative de limite de taille d'un paste.
Default = 500000 octets (500 ko)
Command line equivalent: None
Command line equivalent: None

View File

@ -0,0 +1,100 @@
==============================
zerobinpaste command-line tool
==============================
zerobinpaste is a simple CLI tool (analogous to pastebinit or wgetpaste) to use
with files or shell redirection in terminal or simple scripts.
Example use-cases might look like::
% zerobinpaste README.rst
http://some.0bin.site/paste/0cc3d8a8...
% grep error /var/log/syslog | zerobinpaste
http://some.0bin.site/paste/81fd1324...
% zerobinpaste docs/en/*.rst
easy_install.rst http://some.0bin.site/paste/9adc576a...
apache_install.rst http://some.0bin.site/paste/01408cbd...
options.rst http://some.0bin.site/paste/921b2768...
...
% ps axlf | zerobinpaste | mail -s "Process tree on $(date)" root
Produced links can then be copy-pasted to some IRC channel or used in whatever
other conceivable way.
Tool does encryption by itself on the client machine and key (after hashmark) is
never sent to server or anywhere but the tool's stdout stream (e.g. terminal).
Tool has to be built with `node.js`_ separately (see below).
Usage
=====
At least the pastebin site (main URL where you'd paste stuff with the browser)
has to be specified for the tool to use either via -u (--url) option (can be
simplified with shell alias - e.g. ``alias zp='zerobinpaste -u http://some.0bin.site``)
or in the "~/.zerobinpasterc" configuration file (json format).
| Non-option arguments are interpreted as files to upload/paste contents of.
| If no arguments are specified, data to paste will be read from stdin stream.
Simple configuration file may look like this:
{"url": "http://some.0bin.site"}
Any options (in the long form, e.g. "url" for --url above) that are allowed on
the command-line can be specified there.
Run the tool with -h or --help option to see full list of supported parameters.
Build / Installation
====================
In essence:
0bin% cd tools
0bin/tools% make
...
0bin/tools% cp zerobinpaste ~/bin # install to PATH
"npm" binary (packaged and installed with node.js) is required to pull in build
dependencies, if necessary, and "node" binary is required for produced binary to
run.
Use "make" in "tools" path to produce non-minified runnable "zerobinpaste"
script there.
``make ugly`` command can be used instead of ``make`` to create "minified"
version (using/installing uglifyjs_, about 25% smaller in size).
Resulting "zerobinpaste" script requires only node.js ("node" binary) installed
to run and can be placed in any of the PATH dirs (e.g. "~/bin",
"/usr/local/bin") to be run just as "zerobinpaste".
Why node.js and not python
==========================
Unfortunately, it's fairly complex and unreliable to replicate non-trivial and
undocumented encryption protocol that SJCL_ convenience methods employ, and any
mistake in encryption is guaranteed to produce unreadable paste.
Current implementation uses same JavaScript code (and V8 node.js engine) that
browsers do, hence can be fairly simple and robust.
Future development plans include supporting configurable, less complex and more
widespread encryption schemas, allowing for simplier non-javascript client as
well.
See `related pull request`_ for more details.
.. _node.js: http://nodejs.org/
.. _uglifyjs: https://github.com/mishoo/UglifyJS
.. _SJCL: http://crypto.stanford.edu/sjcl/
.. _related pull request: https://github.com/sametmax/0bin/pull/39

View File

@ -33,8 +33,8 @@ Les serveurs Web Python modernes fonctionnent tous de la même manière, en suiv
une norme d'interfaçage: WSGI.
C'est la solution la plus performante, et celle recommandée. Mais elle demande
l'installation du modle Apache mod_wsgi. Si vous ne savez pas comment faire,
ou si vous ne pouvez pas le faire (par exemple sur un hébergement mutualisé
l'installation du model Apache mod_wsgi. Si vous ne savez pas comment faire,
ou si vous ne pouvez pas le faire (par example sur un hébergement mutualisé
qui ne le propose pas), il vous faudra choisir l'installation CGI.
Premièrement, assurez-vous d'avoir mod_wsgi installé et chargé (en tant qu'admin)::

View File

@ -18,8 +18,8 @@ Entrez::
Installation en 30 secondes (pour tous)
=======================================
- Assurez-vous d'avoir Python 2.6 ou 2.7 (`python --version`)
- Télécharger le dernier zip du code source.
- Assurez-vous d'avoir Python 2.7 ou 3.4+ (`python --version`)
- Télécharger le dernier `zip du code source <https://github.com/sametmax/0bin/zipball/master>`_.
- Décompressez tous les fichiers là où vous souhaitez mettre le site.
- Allez dans les dossiers extraits.
- Lancez avec les droits admin::
@ -40,7 +40,7 @@ solutions.
*Pour un petit site:*
Lancer simplement 0bin en processus shell d'arrière plan. Exemple sous GNU/Linux::
Lancer simplement 0bin en processus shell d'arrière plan. Example sous GNU/Linux::
nohup python zerobin.py --host 0.0.0.0 --port 80 --compressed-static &

View File

@ -0,0 +1,96 @@
=======================================
Outil en ligne de commande zerobinpaste
=======================================
zerobinpaste est un simple outil CLI (similaire à pastebinit ou wgetpaste) à utiliser
avec des fichiers ou des redirection shell dans le terminal ou des scripts simples.
Exemple de cas d'utilisation::
% zerobinpaste README.rst
http://some.0bin.site/paste/0cc3d8a8...
% grep error /var/log/syslog | zerobinpaste
http://some.0bin.site/paste/81fd1324...
% zerobinpaste docs/en/*.rst
easy_install.rst http://some.0bin.site/paste/9adc576a...
apache_install.rst http://some.0bin.site/paste/01408cbd...
options.rst http://some.0bin.site/paste/921b2768...
...
% ps axlf | zerobinpaste | mail -s "Process tree on $(date)" root
Les liens produits peuvent être ensuite copier-coller sur un channel IRC
ou autre.
L'outil produit le chiffrement lui-même sur la machine et la clé (après le hash)
n'est jamais envoyée au serveur ou nul par ailleurs excepté la sortie standard
de l'outil (ex: le terminal).
L'outil doit être buildé avec `node.js`_ séparément (voir plus bas).
Usage
=====
Au minimum il faut préciser le site pastebin (l'url principal d'où on posterait
dans le navigateur) doit être spécifié à l'outil via l'option -u (--url) (on peut
le simplifier avec un alias shell - ex: ``alias zp='zerobinpaste -u http://some.0bin.site``)
ou dans le fichier de configuration "~/.zerobinpasterc" (format json).
| Les arguments positionels sont interprétés comme des fichiers à uploader et chiffrer.
| Si aucun argument n'est passé, le script tentera de lire stdin.
Le fichier de configuration le plus simple pourrait ressembler à celà:
{"url": "http://some.0bin.site"}
Toute option (dans sa forme longue, ex: "url pour --url) utilisable en ligne de commande
peut être spécifié ici.
Lancez l'outil avec -h ou --help pour voir la liste des paramètres supportés.
Build / Installation
====================
En bref:
0bin% cd tools
0bin/tools% make
...
0bin/tools% cp zerobinpaste ~/bin # install to PATH
La commande "npm" (packagé and installé avec node.js) est requise pour télécharger
les dépendances indispensables à la production de l'éxécutable.
Utilisez "make" dans le dossier "tools" pour produire une version non-minifié" de "zerobinpaste".
La commande ``make ugly`` peut être utilisé à la place de ``make`` pour créer une version "minified"
(requière l'installation de uglifyjs_, script produit environ 25% plus petit en taille).
Le script "zerobinpaste" ainsi produit seulement besoin de node.js (et la commande "node") pour
s'éxcuter et peut être placé n'importe où dans le PATH système (ex : "~/bin", "/usr/local/bin")
afin d'être exécuté en tapant simplement "zerobinpaste".
Pourquoi node.js et pas python
==============================
Malheureusement, il est assez difficile et peu fiable de répliquer un protocole
chiffrement non trivial et non documenté tel que celui de certaines méthodes
de SJCL_, et la moindre erreur garantie de produire une paste illisible.
L'implémentation actuelle utilise le même code Javascript (via le moteur V8 de node.js)
que le navigateur, du coup il est simple et robuste.
Il est prévu de supporter plus tard un schéma de chiffrement plus configurable, moins
complexe et plus courant, permettant à des clients non-javascript de fonctioner également.
Voir le `pull request concerné`_ pour plus de détails.
.. _node.js: http://nodejs.org/
.. _uglifyjs: https://github.com/mishoo/UglifyJS
.. _SJCL: http://crypto.stanford.edu/sjcl/
.. _pull request concerné: https://github.com/sametmax/0bin/pull/39

View File

@ -22,10 +22,11 @@
| en/using_supervisor | fr/using_supervisor |
| en/theming | fr/theming |
| en/options | fr/options |
| en/zerobinpaste_tool | fr/zerobinpaste_tool |
| | |
|`Report a bug`_ |`Signaler un bug`_ |
+-------------------------+--------------------------------+
.. _Signaler un bug: https://github.com/sametmax/0bin/issues
.. _Report a bug: <https://github.com/sametmax/0bin/issues>
.. _Report a bug: https://github.com/sametmax/0bin/issues

View File

@ -4,7 +4,7 @@
*
* Sphinx stylesheet -- basic theme.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@ -89,6 +89,7 @@ div.sphinxsidebar #searchbox input[type="submit"] {
img {
border: 0;
max-width: 100%;
}
/* -- search page ----------------------------------------------------------- */
@ -196,7 +197,10 @@ h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink {
dt:hover > a.headerlink,
caption:hover > a.headerlink,
p.caption:hover > a.headerlink,
div.code-block-caption:hover > a.headerlink {
visibility: visible;
}
@ -313,6 +317,13 @@ table.docutils {
border-collapse: collapse;
}
table caption span.caption-number {
font-style: italic;
}
table caption span.caption-text {
}
table.docutils td, table.docutils th {
padding: 1px 8px 1px 5px;
border-top: 0;
@ -343,6 +354,25 @@ table.citation td {
border-bottom: none;
}
/* -- figures --------------------------------------------------------------- */
div.figure {
margin: 0.5em;
padding: 0.5em;
}
div.figure p.caption {
padding: 0.3em;
}
div.figure p.caption span.caption-number {
font-style: italic;
}
div.figure p.caption span.caption-text {
}
/* -- other body styles ----------------------------------------------------- */
ol.arabic {
@ -401,14 +431,14 @@ dl.glossary dt {
margin: 0;
}
.refcount {
color: #060;
}
.optional {
font-size: 1.3em;
}
.sig-paren {
font-size: larger;
}
.versionmodified {
font-style: italic;
}
@ -474,22 +504,51 @@ table.highlighttable td {
padding: 0 0.5em 0 0.5em;
}
tt.descname {
div.code-block-caption {
padding: 2px 5px;
font-size: small;
}
div.code-block-caption code {
background-color: transparent;
}
div.code-block-caption + div > div.highlight > pre {
margin-top: 0;
}
div.code-block-caption span.caption-number {
padding: 0.1em 0.3em;
font-style: italic;
}
div.code-block-caption span.caption-text {
}
div.literal-block-wrapper {
padding: 1em 1em 0;
}
div.literal-block-wrapper div.highlight {
margin: 0;
}
code.descname {
background-color: transparent;
font-weight: bold;
font-size: 1.2em;
}
tt.descclassname {
code.descclassname {
background-color: transparent;
}
tt.xref, a tt {
code.xref, a code {
background-color: transparent;
font-weight: bold;
}
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
background-color: transparent;
}

View File

@ -0,0 +1,261 @@
/*
* default.css_t
* ~~~~~~~~~~~~~
*
* Sphinx stylesheet -- default theme.
*
* :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: sans-serif;
font-size: 100%;
background-color: #11303d;
color: #000;
margin: 0;
padding: 0;
}
div.document {
background-color: #1c4e63;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 230px;
}
div.body {
background-color: #ffffff;
color: #000000;
padding: 0 20px 30px 20px;
}
div.footer {
color: #ffffff;
width: 100%;
padding: 9px 0 9px 0;
text-align: center;
font-size: 75%;
}
div.footer a {
color: #ffffff;
text-decoration: underline;
}
div.related {
background-color: #133f52;
line-height: 30px;
color: #ffffff;
}
div.related a {
color: #ffffff;
}
div.sphinxsidebar {
}
div.sphinxsidebar h3 {
font-family: 'Trebuchet MS', sans-serif;
color: #ffffff;
font-size: 1.4em;
font-weight: normal;
margin: 0;
padding: 0;
}
div.sphinxsidebar h3 a {
color: #ffffff;
}
div.sphinxsidebar h4 {
font-family: 'Trebuchet MS', sans-serif;
color: #ffffff;
font-size: 1.3em;
font-weight: normal;
margin: 5px 0 0 0;
padding: 0;
}
div.sphinxsidebar p {
color: #ffffff;
}
div.sphinxsidebar p.topless {
margin: 5px 10px 10px 10px;
}
div.sphinxsidebar ul {
margin: 10px;
padding: 0;
color: #ffffff;
}
div.sphinxsidebar a {
color: #98dbcc;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
/* -- hyperlink styles ------------------------------------------------------ */
a {
color: #355f7c;
text-decoration: none;
}
a:visited {
color: #355f7c;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* -- body styles ----------------------------------------------------------- */
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Trebuchet MS', sans-serif;
background-color: #f2f2f2;
font-weight: normal;
color: #20435c;
border-bottom: 1px solid #ccc;
margin: 20px -20px 10px -20px;
padding: 3px 0 3px 10px;
}
div.body h1 { margin-top: 0; font-size: 200%; }
div.body h2 { font-size: 160%; }
div.body h3 { font-size: 140%; }
div.body h4 { font-size: 120%; }
div.body h5 { font-size: 110%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: #c60f0f;
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
}
a.headerlink:hover {
background-color: #c60f0f;
color: white;
}
div.body p, div.body dd, div.body li {
text-align: justify;
line-height: 130%;
}
div.admonition p.admonition-title + p {
display: inline;
}
div.admonition p {
margin-bottom: 5px;
}
div.admonition pre {
margin-bottom: 5px;
}
div.admonition ul, div.admonition ol {
margin-bottom: 5px;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre {
padding: 5px;
background-color: #eeffcc;
color: #333333;
line-height: 120%;
border: 1px solid #ac9;
border-left: none;
border-right: none;
}
code {
background-color: #ecf0f3;
padding: 0 1px 0 1px;
font-size: 0.95em;
}
th {
background-color: #ede;
}
.warning code {
background: #efc2c2;
}
.note code {
background: #d6d6d6;
}
.viewcode-back {
font-family: sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}
div.code-block-caption {
color: #efefef;
background-color: #1c4e63;
}

View File

@ -4,7 +4,7 @@
*
* Sphinx stylesheet -- default theme.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/

View File

@ -4,7 +4,7 @@
*
* Sphinx JavaScript utilities for all documentation.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@ -32,7 +32,7 @@ if (!window.console || !console.firebug) {
*/
jQuery.urldecode = function(x) {
return decodeURIComponent(x).replace(/\+/g, ' ');
}
};
/**
* small helper function to urlencode strings
@ -61,18 +61,6 @@ jQuery.getQueryParameters = function(s) {
return result;
};
/**
* small function to check if an array contains
* a given item.
*/
jQuery.contains = function(arr, item) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] == item)
return true;
}
return false;
};
/**
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
@ -103,6 +91,30 @@ jQuery.fn.highlightText = function(text, className) {
});
};
/*
* backward compatibility for jQuery.browser
* This will be supported until firefox bug is fixed.
*/
if (!jQuery.browser) {
jQuery.uaMatch = function(ua) {
ua = ua.toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
[];
return {
browser: match[ 1 ] || "",
version: match[ 2 ] || "0"
};
};
jQuery.browser = {};
jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
}
/**
* Small JavaScript module for the documentation.
*/
@ -164,9 +176,10 @@ var Documentation = {
/**
* workaround a firefox stupidity
* see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
*/
fixFirefoxAnchorBug : function() {
if (document.location.hash && $.browser.mozilla)
if (document.location.hash)
window.setTimeout(function() {
document.location.href += '';
}, 10);
@ -180,6 +193,9 @@ var Documentation = {
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
if (terms.length) {
var body = $('div.body');
if (!body.length) {
body = $('body');
}
window.setTimeout(function() {
$.each(terms, function() {
body.highlightText(this.toLowerCase(), 'highlighted');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 358 B

10308
docs/.build/html/_static/jquery-1.11.1.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 173 B

View File

@ -13,11 +13,11 @@
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #303030 } /* Generic.Output */
.highlight .go { color: #333333 } /* Generic.Output */
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0040D0 } /* Generic.Traceback */
.highlight .gt { color: #0044DD } /* Generic.Traceback */
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
@ -40,6 +40,7 @@
.highlight .nv { color: #bb60d5 } /* Name.Variable */
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #208050 } /* Literal.Number.Bin */
.highlight .mf { color: #208050 } /* Literal.Number.Float */
.highlight .mh { color: #208050 } /* Literal.Number.Hex */
.highlight .mi { color: #208050 } /* Literal.Number.Integer */

View File

@ -4,38 +4,11 @@
*
* Sphinx JavaScript utilties for the full-text search.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/**
* helper function to return a node containing the
* search summary for a given text. keywords is a list
* of stemmed words, hlwords is the list of normal, unstemmed
* words. the first one is used to find the occurance, the
* latter for highlighting it.
*/
jQuery.makeSearchSummary = function(text, keywords, hlwords) {
var textLower = text.toLowerCase();
var start = 0;
$.each(keywords, function() {
var i = textLower.indexOf(this.toLowerCase());
if (i > -1)
start = i;
});
start = Math.max(start - 120, 0);
var excerpt = ((start > 0) ? '...' : '') +
$.trim(text.substr(start, 240)) +
((start + 240 - text.length) ? '...' : '');
var rv = $('<div class="context"></div>').text(excerpt);
$.each(hlwords, function() {
rv = rv.highlightText(this, 'highlighted');
});
return rv;
}
/**
* Porter Stemmer
@ -220,6 +193,38 @@ var Stemmer = function() {
}
/**
* Simple result scoring code.
*/
var Scorer = {
// Implement the following function to further tweak the score for each result
// The function takes a result array [filename, title, anchor, descr, score]
// and returns the new score.
/*
score: function(result) {
return result[4];
},
*/
// query matches the full name of an object
objNameMatch: 11,
// or matches in the last dotted part of the object name
objPartialMatch: 6,
// Additive scores depending on the priority of the object
objPrio: {0: 15, // used to be importantResults
1: 5, // used to be objectResults
2: -5}, // used to be unimportantResults
// Used when the priority is not in the mapping.
objPrioDefault: 0,
// query found in title
title: 15,
// query found in terms
term: 5
};
/**
* Search Module
*/
@ -239,8 +244,13 @@ var Search = {
},
loadIndex : function(url) {
$.ajax({type: "GET", url: url, data: null, success: null,
dataType: "script", cache: true});
$.ajax({type: "GET", url: url, data: null,
dataType: "script", cache: true,
complete: function(jqxhr, textstatus) {
if (textstatus != "success") {
document.getElementById("searchindexloader").src = url;
}
}});
},
setIndex : function(index) {
@ -268,19 +278,20 @@ var Search = {
if (this._pulse_status >= 0)
return;
function pulse() {
var i;
Search._pulse_status = (Search._pulse_status + 1) % 4;
var dotString = '';
for (var i = 0; i < Search._pulse_status; i++)
for (i = 0; i < Search._pulse_status; i++)
dotString += '.';
Search.dots.text(dotString);
if (Search._pulse_status > -1)
window.setTimeout(pulse, 500);
};
}
pulse();
},
/**
* perform a search for something
* perform a search for something (or wait until index is loaded)
*/
performSearch : function(query) {
// create the required interface elements
@ -300,41 +311,46 @@ var Search = {
this.deferQuery(query);
},
/**
* execute search (requires search index to be loaded)
*/
query : function(query) {
var stopwords = ["and","then","into","it","as","are","in","if","for","no","there","their","was","is","be","to","that","but","they","not","such","with","by","a","on","these","of","will","this","near","the","or","at"];
var i;
var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"];
// Stem the searchterms and add them to the correct list
// stem the searchterms and add them to the correct list
var stemmer = new Stemmer();
var searchterms = [];
var excluded = [];
var hlterms = [];
var tmp = query.split(/\s+/);
var objectterms = [];
for (var i = 0; i < tmp.length; i++) {
if (tmp[i] != "") {
for (i = 0; i < tmp.length; i++) {
if (tmp[i] !== "") {
objectterms.push(tmp[i].toLowerCase());
}
if ($u.indexOf(stopwords, tmp[i]) != -1 || tmp[i].match(/^\d+$/) ||
tmp[i] == "") {
if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||
tmp[i] === "") {
// skip this "word"
continue;
}
// stem the word
var word = stemmer.stemWord(tmp[i]).toLowerCase();
var word = stemmer.stemWord(tmp[i].toLowerCase());
var toAppend;
// select the correct list
if (word[0] == '-') {
var toAppend = excluded;
toAppend = excluded;
word = word.substr(1);
}
else {
var toAppend = searchterms;
toAppend = searchterms;
hlterms.push(tmp[i].toLowerCase());
}
// only add if not already in the list
if (!$.contains(toAppend, word))
if (!$u.contains(toAppend, word))
toAppend.push(word);
};
}
var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
// console.debug('SEARCH: searching for:');
@ -342,89 +358,51 @@ var Search = {
// console.info('excluded: ', excluded);
// prepare search
var filenames = this._index.filenames;
var titles = this._index.titles;
var terms = this._index.terms;
var fileMap = {};
var files = null;
// different result priorities
var importantResults = [];
var objectResults = [];
var regularResults = [];
var unimportantResults = [];
var titleterms = this._index.titleterms;
// array of [filename, title, anchor, descr, score]
var results = [];
$('#search-progress').empty();
// lookup as object
for (var i = 0; i < objectterms.length; i++) {
var others = [].concat(objectterms.slice(0,i),
objectterms.slice(i+1, objectterms.length))
var results = this.performObjectSearch(objectterms[i], others);
// Assume first word is most likely to be the object,
// other words more likely to be in description.
// Therefore put matches for earlier words first.
// (Results are eventually used in reverse order).
objectResults = results[0].concat(objectResults);
importantResults = results[1].concat(importantResults);
unimportantResults = results[2].concat(unimportantResults);
for (i = 0; i < objectterms.length; i++) {
var others = [].concat(objectterms.slice(0, i),
objectterms.slice(i+1, objectterms.length));
results = results.concat(this.performObjectSearch(objectterms[i], others));
}
// perform the search on the required terms
for (var i = 0; i < searchterms.length; i++) {
var word = searchterms[i];
// no match but word was a required one
if ((files = terms[word]) == null)
break;
if (files.length == undefined) {
files = [files];
}
// create the mapping
for (var j = 0; j < files.length; j++) {
var file = files[j];
if (file in fileMap)
fileMap[file].push(word);
else
fileMap[file] = [word];
}
// lookup as search terms in fulltext
results = results.concat(this.performTermsSearch(searchterms, excluded, terms, Scorer.term))
.concat(this.performTermsSearch(searchterms, excluded, titleterms, Scorer.title));
// let the scorer override scores with a custom scoring function
if (Scorer.score) {
for (i = 0; i < results.length; i++)
results[i][4] = Scorer.score(results[i]);
}
// now check if the files don't contain excluded terms
for (var file in fileMap) {
var valid = true;
// check if all requirements are matched
if (fileMap[file].length != searchterms.length)
continue;
// ensure that none of the excluded terms is in the
// search result.
for (var i = 0; i < excluded.length; i++) {
if (terms[excluded[i]] == file ||
$.contains(terms[excluded[i]] || [], file)) {
valid = false;
break;
}
// now sort the results by score (in opposite order of appearance, since the
// display function below uses pop() to retrieve items) and then
// alphabetically
results.sort(function(a, b) {
var left = a[4];
var right = b[4];
if (left > right) {
return 1;
} else if (left < right) {
return -1;
} else {
// same score: sort alphabetically
left = a[1].toLowerCase();
right = b[1].toLowerCase();
return (left > right) ? -1 : ((left < right) ? 1 : 0);
}
// if we have still a valid result we can add it
// to the result list
if (valid)
regularResults.push([filenames[file], titles[file], '', null]);
}
// delete unused variables in order to not waste
// memory until list is retrieved completely
delete filenames, titles, terms;
// now sort the regular results descending by title
regularResults.sort(function(a, b) {
var left = a[1].toLowerCase();
var right = b[1].toLowerCase();
return (left > right) ? -1 : ((left < right) ? 1 : 0);
});
// combine all results
var results = unimportantResults.concat(regularResults)
.concat(objectResults).concat(importantResults);
// for debugging
//Search.lastresults = results.slice(); // a copy
//console.info('search results:', Search.lastresults);
// print the results
var resultCount = results.length;
@ -433,7 +411,7 @@ var Search = {
if (results.length) {
var item = results.pop();
var listItem = $('<li style="display:none"></li>');
if (DOCUMENTATION_OPTIONS.FILE_SUFFIX == '') {
if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {
// dirhtml builder
var dirname = item[0] + '/';
if (dirname.match(/\/index\/$/)) {
@ -457,16 +435,18 @@ var Search = {
displayNextItem();
});
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
$.get(DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' +
item[0] + '.txt', function(data) {
if (data != '') {
listItem.append($.makeSearchSummary(data, searchterms, hlterms));
Search.output.append(listItem);
}
listItem.slideDown(5, function() {
displayNextItem();
});
}, "text");
$.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[0] + '.txt',
dataType: "text",
complete: function(jqxhr, textstatus) {
var data = jqxhr.responseText;
if (data !== '' && data !== undefined) {
listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
}
Search.output.append(listItem);
listItem.slideDown(5, function() {
displayNextItem();
});
}});
} else {
// no source available, just display title
Search.output.append(listItem);
@ -489,20 +469,32 @@ var Search = {
displayNextItem();
},
/**
* search for object names
*/
performObjectSearch : function(object, otherterms) {
var filenames = this._index.filenames;
var objects = this._index.objects;
var objnames = this._index.objnames;
var titles = this._index.titles;
var importantResults = [];
var objectResults = [];
var unimportantResults = [];
var i;
var results = [];
for (var prefix in objects) {
for (var name in objects[prefix]) {
var fullname = (prefix ? prefix + '.' : '') + name;
if (fullname.toLowerCase().indexOf(object) > -1) {
var score = 0;
var parts = fullname.split('.');
// check for different match types: exact matches of full name or
// "last name" (i.e. last dotted part)
if (fullname == object || parts[parts.length - 1] == object) {
score += Scorer.objNameMatch;
// matches in last name
} else if (parts[parts.length - 1].indexOf(object) > -1) {
score += Scorer.objPartialMatch;
}
var match = objects[prefix][name];
var objname = objnames[match[1]][2];
var title = titles[match[0]];
@ -512,7 +504,7 @@ var Search = {
var haystack = (prefix + ' ' + name + ' ' +
objname + ' ' + title).toLowerCase();
var allfound = true;
for (var i = 0; i < otherterms.length; i++) {
for (i = 0; i < otherterms.length; i++) {
if (haystack.indexOf(otherterms[i]) == -1) {
allfound = false;
break;
@ -523,37 +515,107 @@ var Search = {
}
}
var descr = objname + _(', in ') + title;
anchor = match[3];
if (anchor == '')
var anchor = match[3];
if (anchor === '')
anchor = fullname;
else if (anchor == '-')
anchor = objnames[match[1]][1] + '-' + fullname;
result = [filenames[match[0]], fullname, '#'+anchor, descr];
switch (match[2]) {
case 1: objectResults.push(result); break;
case 0: importantResults.push(result); break;
case 2: unimportantResults.push(result); break;
// add custom score for some objects according to scorer
if (Scorer.objPrio.hasOwnProperty(match[2])) {
score += Scorer.objPrio[match[2]];
} else {
score += Scorer.objPrioDefault;
}
results.push([filenames[match[0]], fullname, '#'+anchor, descr, score]);
}
}
}
// sort results descending
objectResults.sort(function(a, b) {
return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
});
return results;
},
importantResults.sort(function(a, b) {
return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
});
/**
* search for full-text terms in the index
*/
performTermsSearch : function(searchterms, excluded, terms, score) {
var filenames = this._index.filenames;
var titles = this._index.titles;
unimportantResults.sort(function(a, b) {
return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
});
var i, j, file, files;
var fileMap = {};
var results = [];
return [importantResults, objectResults, unimportantResults]
// perform the search on the required terms
for (i = 0; i < searchterms.length; i++) {
var word = searchterms[i];
// no match but word was a required one
if ((files = terms[word]) === undefined)
break;
if (files.length === undefined) {
files = [files];
}
// create the mapping
for (j = 0; j < files.length; j++) {
file = files[j];
if (file in fileMap)
fileMap[file].push(word);
else
fileMap[file] = [word];
}
}
// now check if the files don't contain excluded terms
for (file in fileMap) {
var valid = true;
// check if all requirements are matched
if (fileMap[file].length != searchterms.length)
continue;
// ensure that none of the excluded terms is in the search result
for (i = 0; i < excluded.length; i++) {
if (terms[excluded[i]] == file ||
$u.contains(terms[excluded[i]] || [], file)) {
valid = false;
break;
}
}
// if we have still a valid result we can add it to the result list
if (valid) {
results.push([filenames[file], titles[file], '', null, score]);
}
}
return results;
},
/**
* helper function to return a node containing the
* search summary for a given text. keywords is a list
* of stemmed words, hlwords is the list of normal, unstemmed
* words. the first one is used to find the occurance, the
* latter for highlighting it.
*/
makeSearchSummary : function(text, keywords, hlwords) {
var textLower = text.toLowerCase();
var start = 0;
$.each(keywords, function() {
var i = textLower.indexOf(this.toLowerCase());
if (i > -1)
start = i;
});
start = Math.max(start - 120, 0);
var excerpt = ((start > 0) ? '...' : '') +
$.trim(text.substr(start, 240)) +
((start + 240 - text.length) ? '...' : '');
var rv = $('<div class="context"></div>').text(excerpt);
$.each(hlwords, function() {
rv = rv.highlightText(this, 'highlighted');
});
return rv;
}
}
};
$(document).ready(function() {
Search.init();

View File

@ -16,12 +16,20 @@
* Once the browser is closed the cookie is deleted and the position
* reset to the default (expanded).
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
$(function() {
// global elements used by the functions.
// the 'sidebarbutton' element is defined as global after its
// creation, in the add_sidebar_button function
@ -134,7 +142,7 @@ $(function() {
var items = document.cookie.split(';');
for(var k=0; k<items.length; k++) {
var key_val = items[k].split('=');
var key = key_val[0];
var key = key_val[0].replace(/ /, ""); // strip leading spaces
if (key == 'sidebar') {
var value = key_val[1];
if ((value == 'collapsed') && (!sidebar_is_collapsed()))
@ -148,4 +156,4 @@ $(function() {
add_sidebar_button();
var sidebarbutton = $('#sidebarbutton');
set_position_from_cookie();
});
});

View File

@ -0,0 +1,999 @@
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root['_'] = _;
}
// Current version.
_.VERSION = '1.3.1';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
if (obj.length === +obj.length) results.length = obj.length;
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = _.toArray(obj).reverse();
if (context && !initial) iterator = _.bind(iterator, context);
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
});
return found;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Shuffle an array.
_.shuffle = function(obj) {
var shuffled = [], rand;
each(obj, function(value, index, list) {
if (index == 0) {
shuffled[0] = value;
} else {
rand = Math.floor(Math.random() * (index + 1));
shuffled[index] = shuffled[rand];
shuffled[rand] = value;
}
});
return shuffled;
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, iterator, context) {
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) {
var result = {};
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
return result;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.toArray(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head`. The **guard** check allows it to work
// with `_.map`.
_.first = _.head = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// Returns everything but the last entry of the array. Especcialy useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0));
} else {
return array[array.length - 1];
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, function(value){ return !!value; });
};
// Return a completely flattened version of an array.
_.flatten = function(array, shallow) {
return _.reduce(array, function(memo, value) {
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
memo[memo.length] = value;
return memo;
}, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
var result = [];
_.reduce(initial, function(memo, el, i) {
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
memo[memo.length] = el;
result[result.length] = array[i];
}
return memo;
}, []);
return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments, true));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_.intersection = _.intersect = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = _.flatten(slice.call(arguments, 1));
return _.filter(array, function(value){ return !_.include(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
return results;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (i in array && array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(func, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) func.apply(context, args);
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
func.apply(context, args);
}
whenDone();
throttling = true;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds.
_.debounce = function(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Internal recursive comparison function.
function eq(a, b, stack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
if (_.has(b, key) && !(size--)) break;
}
result = !size;
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
return result;
}
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Is a given variable an arguments object?
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee'));
};
}
// Is a given value a function?
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// Is a given value a string?
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// Is a given value a number?
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// Is the given value `NaN`?
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
return obj !== obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Is a given value a date?
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// Is the given value a regular expression?
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Has own property?
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
// Run a function **n** times.
_.times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Escape a string for HTML interpolation.
_.escape = function(string) {
return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /.^/;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var unescape = function(code) {
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(str, data) {
var c = _.templateSettings;
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
'with(obj||{}){__p.push(\'' +
str.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(c.escape || noMatch, function(match, code) {
return "',_.escape(" + unescape(code) + "),'";
})
.replace(c.interpolate || noMatch, function(match, code) {
return "'," + unescape(code) + ",'";
})
.replace(c.evaluate || noMatch, function(match, code) {
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
})
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
+ "');}return __p.join('');";
var func = new Function('obj', '_', tmpl);
if (data) return func(data, _);
return function(data) {
return func.call(this, data, _);
};
};
// Add a "chain" function, which will delegate to the wrapper.
_.chain = function(obj) {
return _(obj).chain();
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
return result(wrapped, this._chain);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);

View File

@ -1,23 +1,31 @@
// Underscore.js 0.5.5
// (c) 2009 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the terms of the MIT license.
// Portions of Underscore are inspired by or borrowed from Prototype.js,
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore/
(function(){var j=this,n=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,o=Array.prototype.unshift,p=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.5";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isArray(a)||b.isArguments(a))for(var e=0,f=a.length;e<f;e++)c.call(d,
a[e],e,a);else{var g=b.keys(a);f=g.length;for(e=0;e<f;e++)c.call(d,a[g[e]],g[e],a)}}catch(h){if(h!=m)throw h;}return a};b.map=function(a,c,d){if(a&&b.isFunction(a.map))return a.map(c,d);var e=[];b.each(a,function(f,g,h){e.push(c.call(d,f,g,h))});return e};b.reduce=function(a,c,d,e){if(a&&b.isFunction(a.reduce))return a.reduce(b.bind(d,e),c);b.each(a,function(f,g,h){c=d.call(e,c,f,g,h)});return c};b.reduceRight=function(a,c,d,e){if(a&&b.isFunction(a.reduceRight))return a.reduceRight(b.bind(d,e),c);
var f=b.clone(b.toArray(a)).reverse();b.each(f,function(g,h){c=d.call(e,c,g,h,a)});return c};b.detect=function(a,c,d){var e;b.each(a,function(f,g,h){if(c.call(d,f,g,h)){e=f;b.breakLoop()}});return e};b.select=function(a,c,d){if(a&&b.isFunction(a.filter))return a.filter(c,d);var e=[];b.each(a,function(f,g,h){c.call(d,f,g,h)&&e.push(f)});return e};b.reject=function(a,c,d){var e=[];b.each(a,function(f,g,h){!c.call(d,f,g,h)&&e.push(f)});return e};b.all=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.every))return a.every(c,
d);var e=true;b.each(a,function(f,g,h){(e=e&&c.call(d,f,g,h))||b.breakLoop()});return e};b.any=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.some))return a.some(c,d);var e=false;b.each(a,function(f,g,h){if(e=c.call(d,f,g,h))b.breakLoop()});return e};b.include=function(a,c){if(b.isArray(a))return b.indexOf(a,c)!=-1;var d=false;b.each(a,function(e){if(d=e===c)b.breakLoop()});return d};b.invoke=function(a,c){var d=b.rest(arguments,2);return b.map(a,function(e){return(c?e[c]:e).apply(e,d)})};b.pluck=
function(a,c){return b.map(a,function(d){return d[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g>=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g<e.computed&&(e={value:f,computed:g})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,
function(e,f,g){return{value:e,criteria:c.call(d,e,f,g)}}).sort(function(e,f){e=e.criteria;f=f.criteria;return e<f?-1:e>f?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?(e=g+1):(f=g)}return e};b.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;if(b.isArguments(a))return k.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=function(a,c,d){return c&&!d?k.call(a,
0,c):a[0]};b.rest=function(a,c,d){return k.call(a,b.isUndefined(c)||d?1:c)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.select(a,function(c){return!!c})};b.flatten=function(a){return b.reduce(a,[],function(c,d){if(b.isArray(d))return c.concat(b.flatten(d));c.push(d);return c})};b.without=function(a){var c=b.rest(arguments);return b.select(a,function(d){return!b.include(c,d)})};b.uniq=function(a,c){return b.reduce(a,[],function(d,e,f){if(0==f||(c===true?b.last(d)!=e:!b.include(d,
e)))d.push(e);return d})};b.intersect=function(a){var c=b.rest(arguments);return b.select(b.uniq(a),function(d){return b.all(c,function(e){return b.indexOf(e,d)>=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e<c;e++)d[e]=b.pluck(a,String(e));return d};b.indexOf=function(a,c){if(a.indexOf)return a.indexOf(c);for(var d=0,e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,c){if(a.lastIndexOf)return a.lastIndexOf(c);for(var d=
a.length;d--;)if(a[d]===c)return d;return-1};b.range=function(a,c,d){var e=b.toArray(arguments),f=e.length<=1;a=f?0:e[0];c=f?e[0]:e[1];d=e[2]||1;e=Math.ceil((c-a)/d);if(e<=0)return[];e=new Array(e);f=a;for(var g=0;1;f+=d){if((d>0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)});
return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length);
var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false;
if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length==
0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return!!(a&&a.concat&&a.unshift)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!r.call(a,"length")};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return p.call(a)==="[object Number]"};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&&
a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g,
" ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');");return c?a(c):a};b.forEach=b.each;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.any;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){var d=b.toArray(arguments);
o.call(d,this._wrapped);return l(c.apply(b,d),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})();
// http://documentcloud.github.com/underscore
(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),
function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 B

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 345 B

View File

@ -4,7 +4,7 @@
*
* sphinx.websupport utilties for all documentation.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@ -50,51 +50,51 @@
}
function initEvents() {
$('a.comment-close').live("click", function(event) {
$(document).on("click", 'a.comment-close', function(event) {
event.preventDefault();
hide($(this).attr('id').substring(2));
});
$('a.vote').live("click", function(event) {
$(document).on("click", 'a.vote', function(event) {
event.preventDefault();
handleVote($(this));
});
$('a.reply').live("click", function(event) {
$(document).on("click", 'a.reply', function(event) {
event.preventDefault();
openReply($(this).attr('id').substring(2));
});
$('a.close-reply').live("click", function(event) {
$(document).on("click", 'a.close-reply', function(event) {
event.preventDefault();
closeReply($(this).attr('id').substring(2));
});
$('a.sort-option').live("click", function(event) {
$(document).on("click", 'a.sort-option', function(event) {
event.preventDefault();
handleReSort($(this));
});
$('a.show-proposal').live("click", function(event) {
$(document).on("click", 'a.show-proposal', function(event) {
event.preventDefault();
showProposal($(this).attr('id').substring(2));
});
$('a.hide-proposal').live("click", function(event) {
$(document).on("click", 'a.hide-proposal', function(event) {
event.preventDefault();
hideProposal($(this).attr('id').substring(2));
});
$('a.show-propose-change').live("click", function(event) {
$(document).on("click", 'a.show-propose-change', function(event) {
event.preventDefault();
showProposeChange($(this).attr('id').substring(2));
});
$('a.hide-propose-change').live("click", function(event) {
$(document).on("click", 'a.hide-propose-change', function(event) {
event.preventDefault();
hideProposeChange($(this).attr('id').substring(2));
});
$('a.accept-comment').live("click", function(event) {
$(document).on("click", 'a.accept-comment', function(event) {
event.preventDefault();
acceptComment($(this).attr('id').substring(2));
});
$('a.delete-comment').live("click", function(event) {
$(document).on("click", 'a.delete-comment', function(event) {
event.preventDefault();
deleteComment($(this).attr('id').substring(2));
});
$('a.comment-markup').live("click", function(event) {
$(document).on("click", 'a.comment-markup', function(event) {
event.preventDefault();
toggleCommentMarkupBox($(this).attr('id').substring(2));
});
@ -700,8 +700,8 @@
(<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\
<div class="comment-markup-box" id="mb<%id%>">\
reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \
<tt>``code``</tt>, \
code blocks: <tt>::</tt> and an indented block after blank line</div>\
<code>``code``</code>, \
code blocks: <code>::</code> and an indented block after blank line</div>\
<form method="post" id="cf<%id%>" class="comment-form" action="">\
<textarea name="comment" cols="80"></textarea>\
<p class="propose-button">\

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Apache setup &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Nginx setup" href="nginx_install.html" />
<link rel="prev" title="Easiest installation" href="easy_install.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="easy_install.html" title="Easiest installation"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="apache-setup">
<h1>Apache setup<a class="headerlink" href="#apache-setup" title="Permalink to this headline"></a></h1>
@ -67,12 +65,12 @@
<p>Une installation apache est aussi beaucoup plus solide et sécurisé qu&#8217;une
installation facile. You&#8217;ll benefit from having:</p>
<ul class="simple">
<li>the possiblity to have several projects listening to the port 80;</li>
<li>the possibility to have several projects listening to the port 80;</li>
<li>several Apache module at your disposal (like requests throttling);</li>
<li>Apache robustness in front end: it&#8217;s secure, and there is much less chance
it will crash under heavy load;</li>
<li>your web site processes won&#8217;t run with admin rights, even if
<a class="reference internal" href="options.html#user-and-group-en"><em>&#8211;user</em></a> doesn&#8217;t
<a class="reference internal" href="options.html#user-and-group-en"><span>&#8211;user</span></a> doesn&#8217;t
work on your OS.</li>
</ul>
<div class="section" id="mod-wsgi">
@ -84,13 +82,14 @@ the setup of the Apache module mod_wsgi. If you don&#8217;t know how to do this,
if you can&#8217;t do it (E.G: your hosting won&#8217;t let you), you need to go for
the CGI setup.</p>
<p>First, make sure you have mod_wsgi installed and enable by running (as admin):</p>
<div class="highlight-python"><pre>a2enmod wsgi</pre>
<div class="highlight-python"><div class="highlight"><pre>a2enmod wsgi
</pre></div>
</div>
<p>This enable mod_wsgi. It it doesn&#8217;t, install it first (on ubuntu, the package
is libapache2-mod-wsgi).</p>
<p>Then create an Apache configuration file, usually in /etc/apache/sites-available/.
Name it zerobin:</p>
<div class="highlight-python"><pre>&lt;VirtualHost *:80&gt;
<div class="highlight-python"><div class="highlight"><pre>&lt;VirtualHost *:80&gt;
ServerName www.yourwebsite.com
WSGIDaemonProcess zerobin user=www-data group=www-data processes=1 threads=5
@ -102,13 +101,16 @@ Name it zerobin:</p>
Order deny,allow
Allow from all
&lt;/Directory&gt;
&lt;/VirtualHost&gt;</pre>
&lt;/VirtualHost&gt;
</pre></div>
</div>
<p>Activate the website (as admin):</p>
<div class="highlight-python"><pre>a2ensite zerobin</pre>
<div class="highlight-python"><div class="highlight"><pre>a2ensite zerobin
</pre></div>
</div>
<p>And reload the apache configuration (as admin):</p>
<div class="highlight-python"><pre>service apache2 reload</pre>
<div class="highlight-python"><div class="highlight"><pre>service apache2 reload
</pre></div>
</div>
<p>You&#8217;ll note that we refer to a file named app.wsgi. It&#8217;s a Python file
creating the application Apache is going to use to start the Python process:</p>
@ -147,7 +149,7 @@ it yet. Please contact us if you ever get the need to use it.</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -164,12 +166,14 @@ it yet. Please contact us if you ever get the need to use it.</p>
<h4>Next topic</h4>
<p class="topless"><a href="nginx_install.html"
title="next chapter">Nginx setup</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/apache_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/apache_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -186,7 +190,7 @@ it yet. Please contact us if you ever get the need to use it.</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -198,12 +202,12 @@ it yet. Please contact us if you ever get the need to use it.</p>
<li class="right" >
<a href="easy_install.html" title="Easiest installation"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Easiest installation &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Apache setup" href="apache_install.html" />
<link rel="prev" title="Introduction" href="intro.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="intro.html" title="Introduction"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="easiest-installation">
<h1>Easiest installation<a class="headerlink" href="#easiest-installation" title="Permalink to this headline"></a></h1>
@ -59,28 +57,31 @@ and <a class="reference internal" href="nginx_install.html"><em>Nginx</em></a> s
<div class="section" id="seconds-setup-if-you-know-python-already">
<h2>10 seconds setup (if you know Python already)<a class="headerlink" href="#seconds-setup-if-you-know-python-already" title="Permalink to this headline"></a></h2>
<p>Just type:</p>
<div class="highlight-python"><pre>pip install zerobin
zerobin --host 0.0.0.0 --port 80 --compressed-static # as admin</pre>
<div class="highlight-python"><div class="highlight"><pre>pip install zerobin
zerobin --host 0.0.0.0 --port 80 --compressed-static # as admin
</pre></div>
</div>
</div>
<div class="section" id="seconds-setup-for-anybody">
<h2>30 seconds setup (for anybody)<a class="headerlink" href="#seconds-setup-for-anybody" title="Permalink to this headline"></a></h2>
<ul>
<li><p class="first">Make sure you have Python 2.6 or 2.7 (<cite>python &#8211;version</cite>)</p>
<li><p class="first">Make sure you have Python 2.7 or 3.4+ (<cite>python &#8211;version</cite>)</p>
</li>
<li><p class="first">Download the last zip of the sources code.</p>
<li><p class="first">Download the last <a class="reference external" href="https://github.com/sametmax/0bin/zipball/master">zip of the source code</a></p>
</li>
<li><p class="first">Extract all of it where you wish the site to be stored.</p>
</li>
<li><p class="first">Go to the extracted files.</p>
</li>
<li><p class="first">Run with the admin rights:</p>
<div class="highlight-python"><pre>python zerobin.py --host 0.0.0.0 --port 80 --compressed-static</pre>
<div class="highlight-python"><div class="highlight"><pre>python zerobin.py --host 0.0.0.0 --port 80 --compressed-static
</pre></div>
</div>
</li>
</ul>
<p>On ubuntu, this is a one liner:</p>
<div class="highlight-python"><pre>wget stuff &amp;&amp; unzip zerobin.zip &amp;&amp; cd zerobin &amp;&amp; sudo python zerobin.py --host 0.0.0.0 --port 80 --compressed-static</pre>
<div class="highlight-python"><div class="highlight"><pre>wget stuff &amp;&amp; unzip zerobin.zip &amp;&amp; cd zerobin &amp;&amp; sudo python zerobin.py --host 0.0.0.0 --port 80 --compressed-static
</pre></div>
</div>
<p>Check out for more <a class="reference internal" href="options.html"><em>configuration options</em></a>.</p>
</div>
@ -89,7 +90,8 @@ zerobin --host 0.0.0.0 --port 80 --compressed-static # as admin</pre>
<p>0bin doesn&#8217;t come with something built in for this. You have several solutions.</p>
<p><em>For a small website:</em></p>
<p>Just make it a shell background process. E.G in GNU/Linux:</p>
<div class="highlight-python"><pre>nohup python zerobin.py --host 0.0.0.0 --port 80 --compressed-static &amp;</pre>
<div class="highlight-python"><div class="highlight"><pre>nohup python zerobin.py --host 0.0.0.0 --port 80 --compressed-static &amp;
</pre></div>
</div>
<p>Or run it in a screen.</p>
<p><em>For a big Website:</em></p>
@ -101,7 +103,8 @@ zerobin --host 0.0.0.0 --port 80 --compressed-static # as admin</pre>
<p class="first admonition-title">Note</p>
<p>You can even use zerobin on your private local network from your laptop.</p>
<p>Make sure you firewall won&#8217;t block the port, and run:</p>
<div class="highlight-python"><pre>python zerobin.py --host 0.0.0.0 --port 8000</pre>
<div class="highlight-python"><div class="highlight"><pre>python zerobin.py --host 0.0.0.0 --port 8000
</pre></div>
</div>
<p>0bin will now be accessible from <a class="reference external" href="http://your.local.ip.address:8000">http://your.local.ip.address:8000</a>.</p>
<p class="last">This can be very cool way to share code in a companie or during a code sprint.</p>
@ -113,7 +116,7 @@ zerobin --host 0.0.0.0 --port 80 --compressed-static # as admin</pre>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -131,12 +134,14 @@ zerobin --host 0.0.0.0 --port 80 --compressed-static # as admin</pre>
<h4>Next topic</h4>
<p class="topless"><a href="apache_install.html"
title="next chapter">Apache setup</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/easy_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/easy_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -153,7 +158,7 @@ zerobin --host 0.0.0.0 --port 80 --compressed-static # as admin</pre>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -165,12 +170,12 @@ zerobin --host 0.0.0.0 --port 80 --compressed-static # as admin</pre>
<li class="right" >
<a href="intro.html" title="Introduction"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Introduction &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Easiest installation" href="easy_install.html" />
<link rel="prev" title="0bins documentation" href="../index.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="../index.html" title="0bins documentation"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="introduction">
<h1>Introduction<a class="headerlink" href="#introduction" title="Permalink to this headline"></a></h1>
@ -94,7 +92,8 @@ install even if you know nothing about Python.</p>
<li>copy paste to clipboard in a click;</li>
<li>get paste short URL in a click;</li>
<li>own previous pastes history;</li>
<li>visual hash of a paste to easily tell it appart from others in a list.</li>
<li>visual hash of a paste to easily tell it apart from others in a list;</li>
<li><a class="reference external" href="./zerobinpaste_tool">optional command-line tool</a> to encrypt and paste data from shell or scripts.</li>
</ul>
</div>
<div class="section" id="technologies-used">
@ -107,6 +106,7 @@ install even if you know nothing about Python.</p>
<li><a class="reference external" href="http://twitter.github.com/bootstrap/">Bootstrap</a>, the Twitter HTML5/CSS3 framework</li>
<li><a class="reference external" href="https://github.com/sametmax/VizHash.js">VizHash.js</a> to create visual hashes from pastes</li>
<li><a class="reference external" href="http://www.cherrypy.org/(serveronly)">Cherrypy</a> (server only)</li>
<li><a class="reference external" href="http://nodejs.org/">node.js</a> (for optional command-line tool only)</li>
</ul>
</div>
<div class="section" id="known-issues">
@ -132,7 +132,7 @@ install even if you know nothing about Python.</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -152,12 +152,14 @@ install even if you know nothing about Python.</p>
<h4>Next topic</h4>
<p class="topless"><a href="easy_install.html"
title="next chapter">Easiest installation</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/intro.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/intro.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -174,7 +176,7 @@ install even if you know nothing about Python.</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -186,12 +188,12 @@ install even if you know nothing about Python.</p>
<li class="right" >
<a href="../index.html" title="0bins documentation"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Nginx setup &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Using supervisor" href="using_supervisor.html" />
<link rel="prev" title="Apache setup" href="apache_install.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="apache_install.html" title="Apache setup"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="nginx-setup">
<h1>Nginx setup<a class="headerlink" href="#nginx-setup" title="Permalink to this headline"></a></h1>
@ -76,7 +74,7 @@ the Python server.</p>
</ul>
<p>You will benefit from having:</p>
<ul class="simple">
<li>the possiblity to have several projects listening to the port 80;</li>
<li>the possibility to have several projects listening to the port 80;</li>
<li>several Apache module at your disposal (like requests throttling);</li>
<li>Apache robustness in front end: it&#8217;s secure, and there is much less chance
it will crash under heavy load;</li>
@ -88,9 +86,10 @@ processes. It&#8217;s very handy for updates.</li>
<div class="section" id="the-python-process">
<h2>The Python process<a class="headerlink" href="#the-python-process" title="Permalink to this headline"></a></h2>
<p>Run 0bin as usual, but this time make it listen to a local port and host. E.G:</p>
<div class="highlight-python"><pre>zerobin --host 127.0.0.1 --port 8000</pre>
<div class="highlight-python"><div class="highlight"><pre>zerobin --host 127.0.0.1 --port 8000
</pre></div>
</div>
<p>In PHP, when you edit a file, the changes are immediatly visible. In Python,
<p>In PHP, when you edit a file, the changes are immediately visible. In Python,
the whole code is often loaded in memory for performance reasons. This means
you have to restart the Python process to see the changes effect. Having a
separate process let you do this without having to restart the server.</p>
@ -102,19 +101,20 @@ installing it.</p>
<p>Vous must create a Nginx configuration file for 0bin. On GNU/Linux, they usually
go into /etc/nginx/conf.d/. Name it zerobin.conf.</p>
<p>The minimal configuration file to run the site is:</p>
<div class="highlight-python"><pre>server {
<div class="highlight-python"><div class="highlight"><pre>server {
listen 80;
server_name www.yourwebsite.com;
location / {
proxy_pass http://127.0.0.1:8000;
}
}</pre>
}
</pre></div>
</div>
<p><cite>proxy_pass</cite> just passes the external request to the Python process.
The port much match the one used by the 0bin process of course.</p>
<p>You can make some adjustements to get a better user experience:</p>
<div class="highlight-python"><pre>server {
<div class="highlight-python"><div class="highlight"><pre>server {
listen 80;
server_name www.yourwebsite.com;
@ -139,7 +139,8 @@ The port much match the one used by the 0bin process of course.</p>
location / {
proxy_pass http://zerobin_cherrypy;
}
}</pre>
}
</pre></div>
</div>
<p>This make Nginx serve the favicon and static files, set the expire HTTP headers
and make sure gzip compression is used with browsers that support it.</p>
@ -150,7 +151,7 @@ and make sure gzip compression is used with browsers that support it.</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -167,12 +168,14 @@ and make sure gzip compression is used with browsers that support it.</p>
<h4>Next topic</h4>
<p class="topless"><a href="using_supervisor.html"
title="next chapter">Using supervisor</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/nginx_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/nginx_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -189,7 +192,7 @@ and make sure gzip compression is used with browsers that support it.</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -201,12 +204,12 @@ and make sure gzip compression is used with browsers that support it.</p>
<li class="right" >
<a href="apache_install.html" title="Apache setup"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Options &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -26,30 +24,30 @@
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="0bin 0.1 documentation" href="../index.html" />
<link rel="next" title="Introduction" href="../fr/intro.html" />
<link rel="next" title="zerobinpaste command-line tool" href="zerobinpaste_tool.html" />
<link rel="prev" title="Theming" href="theming.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../fr/intro.html" title="Introduction"
<a href="zerobinpaste_tool.html" title="zerobinpaste command-line tool"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="theming.html" title="Theming"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="options">
<h1>Options<a class="headerlink" href="#options" title="Permalink to this headline"></a></h1>
@ -106,7 +104,8 @@ web site.</p>
<div class="section" id="examples">
<h3>Examples<a class="headerlink" href="#examples" title="Permalink to this headline"></a></h3>
<p>Production:</p>
<div class="highlight-python"><pre>sudo zerobin --host 0.0.0.0 --port 80 --user foo --compressed-static</pre>
<div class="highlight-python"><div class="highlight"><pre>sudo zerobin --host 0.0.0.0 --port 80 --user foo --compressed-static
</pre></div>
</div>
<p>Developpement:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="n">zerobin</span> <span class="o">--</span><span class="n">debug</span> <span class="o">--</span><span class="n">serve</span><span class="o">-</span><span class="n">static</span>
@ -118,7 +117,8 @@ web site.</p>
<h2>Configuration file<a class="headerlink" href="#configuration-file" title="Permalink to this headline"></a></h2>
<p>The configuration file should be an ordinary Python file, usually named
settings.py. It&#8217;s used this way:</p>
<div class="highlight-python"><pre>zerobin --settings-file '/path/to/settings.py'</pre>
<div class="highlight-python"><div class="highlight"><pre>zerobin --settings-file &#39;/path/to/settings.py&#39;
</pre></div>
</div>
<p>Any options passed to the command line will have priority on the ones in
the configuration file. The zerobin/default_settings.py can be used as an
@ -156,7 +156,7 @@ web site.</p>
<p>List of absolute path to directories containing templates that 0bin uses to
generate the web site pages. The first list items have priotity on the later.</p>
<p>If you wish to use your own templates, add the directory containing them
at the begining of the list:</p>
at the beginning of the list:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="kn">from</span> <span class="nn">zerobin.defauls_settings</span> <span class="kn">import</span> <span class="n">TEMPLATE_DIRS</span>
<span class="n">TEMPLATE_DIRS</span> <span class="o">=</span> <span class="p">(</span>
@ -212,7 +212,7 @@ port 80, but you wish that the process do not have access to protected files.</p
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -247,14 +247,16 @@ port 80, but you wish that the process do not have access to protected files.</p
<p class="topless"><a href="theming.html"
title="previous chapter">Theming</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="../fr/intro.html"
title="next chapter">Introduction</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/options.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<p class="topless"><a href="zerobinpaste_tool.html"
title="next chapter">zerobinpaste command-line tool</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/options.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -271,24 +273,24 @@ port 80, but you wish that the process do not have access to protected files.</p
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../fr/intro.html" title="Introduction"
<a href="zerobinpaste_tool.html" title="zerobinpaste command-line tool"
>next</a> |</li>
<li class="right" >
<a href="theming.html" title="Theming"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Theming &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Options" href="options.html" />
<link rel="prev" title="Using supervisor" href="using_supervisor.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,24 +40,24 @@
<li class="right" >
<a href="using_supervisor.html" title="Using supervisor"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="theming">
<h1>Theming<a class="headerlink" href="#theming" title="Permalink to this headline"></a></h1>
<p>0bin comes with a complete theming support, but for now it&#8217;s not well integrated.</p>
<p>If you wish to create your own theme, you&#8217;ll need to create templates similar
to the ones in zerobin/view, and add the path to the directory containing them
to the <a class="reference internal" href="options.html#template-dirs-en"><em>configuration file</em></a>.</p>
to the <a class="reference internal" href="options.html#template-dirs-en"><span>configuration file</span></a>.</p>
<p>You&#8217;ll also need to copy static files from zerobin/static to a new directory
where you can edit them. And you need to add this directory in the
<em class="xref std std-ref">configuration file</em> too.</p>
<a class="reference internal" href="options.html#static-root-en"><span>configuration file</span></a> too.</p>
<p>Of course, if you look for something simple, you can just edit all files in place/</p>
<p>But be careful, the javascript code is tightly coupled with HTML ID and classes,
and they are not very well organized for now.</p>
@ -70,7 +68,7 @@ and they are not very well organized for now.</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h4>Previous topic</h4>
<p class="topless"><a href="using_supervisor.html"
@ -78,12 +76,14 @@ and they are not very well organized for now.</p>
<h4>Next topic</h4>
<p class="topless"><a href="options.html"
title="next chapter">Options</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/theming.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/theming.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -100,7 +100,7 @@ and they are not very well organized for now.</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -112,12 +112,12 @@ and they are not very well organized for now.</p>
<li class="right" >
<a href="using_supervisor.html" title="Using supervisor"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Using supervisor &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Theming" href="theming.html" />
<link rel="prev" title="Nginx setup" href="nginx_install.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="nginx_install.html" title="Nginx setup"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="using-supervisor">
<h1>Using supervisor<a class="headerlink" href="#using-supervisor" title="Permalink to this headline"></a></h1>
@ -57,7 +55,7 @@
the setup (which is just apt-get install supervisor or pip install supervisor
most of the time), but here is a quick overview on how to use it.</p>
<p>Create a configuration file named supervisor.ini:</p>
<div class="highlight-python"><pre>[unix_http_server]
<div class="highlight-python"><div class="highlight"><pre>[unix_http_server]
file=/tmp/supervisor.sock;
[supervisorctl]
@ -80,16 +78,18 @@ user=zerobin
[program:zerobin]
command=/path/to/zerobin/zerobin.py --port 80 --compressed-static
directory=/path/to/zerobin/
environment=PYTHONPATH='/path/to/zerobin/'
environment=PYTHONPATH=&#39;/path/to/zerobin/&#39;
user=zerobin
autostart=true
autorestart=true</pre>
autorestart=true
</pre></div>
</div>
<p>The 4 first entries are just boiler plate to get you started, you can copy
them verbatim.</p>
<p>The last one define one (you can have many) process supervisor should manage.</p>
<p>It means it will run the command:</p>
<div class="highlight-python"><pre>/path/to/zerobin/zerobin.py --port 80 --compressed-static</pre>
<div class="highlight-python"><div class="highlight"><pre>/path/to/zerobin/zerobin.py --port 80 --compressed-static
</pre></div>
</div>
<p>In the directory, with the environnement and the user you defined.</p>
<p>This command will be ran as a daemon, in the background.</p>
@ -109,7 +109,8 @@ running, even it crashes temporarly or if you retart the machine.</p>
<p class="first admonition-title">Note</p>
<p>If you installed zerobin in a virtualenv, you may set the command
to run directly from it:</p>
<div class="last highlight-python"><pre>command=/path/to/virtualenv/bin/zerobin --port 80 --compressed-static</pre>
<div class="last highlight-python"><div class="highlight"><pre>command=/path/to/virtualenv/bin/zerobin --port 80 --compressed-static
</pre></div>
</div>
</div>
</div>
@ -118,7 +119,7 @@ to run directly from it:</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h4>Previous topic</h4>
<p class="topless"><a href="nginx_install.html"
@ -126,12 +127,14 @@ to run directly from it:</p>
<h4>Next topic</h4>
<p class="topless"><a href="theming.html"
title="next chapter">Theming</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/using_supervisor.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/using_supervisor.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -148,7 +151,7 @@ to run directly from it:</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -160,12 +163,12 @@ to run directly from it:</p>
<li class="right" >
<a href="nginx_install.html" title="Nginx setup"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -0,0 +1,194 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>zerobinpaste command-line tool &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '../',
VERSION: '0.1',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="../_static/jquery.js"></script>
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="0bin 0.1 documentation" href="../index.html" />
<link rel="next" title="Introduction" href="../fr/intro.html" />
<link rel="prev" title="Options" href="options.html" />
</head>
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../fr/intro.html" title="Introduction"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="options.html" title="Options"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="zerobinpaste-command-line-tool">
<h1>zerobinpaste command-line tool<a class="headerlink" href="#zerobinpaste-command-line-tool" title="Permalink to this headline"></a></h1>
<p>zerobinpaste is a simple CLI tool (analogous to pastebinit or wgetpaste) to use
with files or shell redirection in terminal or simple scripts.</p>
<p>Example use-cases might look like:</p>
<div class="highlight-python"><div class="highlight"><pre>% zerobinpaste README.rst
http://some.0bin.site/paste/0cc3d8a8...
% grep error /var/log/syslog | zerobinpaste
http://some.0bin.site/paste/81fd1324...
% zerobinpaste docs/en/*.rst
easy_install.rst http://some.0bin.site/paste/9adc576a...
apache_install.rst http://some.0bin.site/paste/01408cbd...
options.rst http://some.0bin.site/paste/921b2768...
...
% ps axlf | zerobinpaste | mail -s &quot;Process tree on $(date)&quot; root
</pre></div>
</div>
<p>Produced links can then be copy-pasted to some IRC channel or used in whatever
other conceivable way.</p>
<p>Tool does encryption by itself on the client machine and key (after hashmark) is
never sent to server or anywhere but the tool&#8217;s stdout stream (e.g. terminal).</p>
<p>Tool has to be built with <a class="reference external" href="http://nodejs.org/">node.js</a> separately (see below).</p>
<div class="section" id="usage">
<h2>Usage<a class="headerlink" href="#usage" title="Permalink to this headline"></a></h2>
<p>At least the pastebin site (main URL where you&#8217;d paste stuff with the browser)
has to be specified for the tool to use either via -u (&#8211;url) option (can be
simplified with shell alias - e.g. <code class="docutils literal"><span class="pre">alias</span> <span class="pre">zp='zerobinpaste</span> <span class="pre">-u</span> <span class="pre">http://some.0bin.site</span></code>)
or in the &#8220;~/.zerobinpasterc&#8221; configuration file (json format).</p>
<div class="line-block">
<div class="line">Non-option arguments are interpreted as files to upload/paste contents of.</div>
<div class="line">If no arguments are specified, data to paste will be read from stdin stream.</div>
</div>
<p>Simple configuration file may look like this:</p>
<blockquote>
<div>{&#8220;url&#8221;: &#8220;<a class="reference external" href="http://some.0bin.site">http://some.0bin.site</a>&#8220;}</div></blockquote>
<p>Any options (in the long form, e.g. &#8220;url&#8221; for &#8211;url above) that are allowed on
the command-line can be specified there.</p>
<p>Run the tool with -h or &#8211;help option to see full list of supported parameters.</p>
</div>
<div class="section" id="build-installation">
<h2>Build / Installation<a class="headerlink" href="#build-installation" title="Permalink to this headline"></a></h2>
<p>In essence:</p>
<blockquote>
<div>0bin% cd tools
0bin/tools% make
...
0bin/tools% cp zerobinpaste ~/bin # install to PATH</div></blockquote>
<p>&#8220;npm&#8221; binary (packaged and installed with node.js) is required to pull in build
dependencies, if necessary, and &#8220;node&#8221; binary is required for produced binary to
run.</p>
<p>Use &#8220;make&#8221; in &#8220;tools&#8221; path to produce non-minified runnable &#8220;zerobinpaste&#8221;
script there.</p>
<p><code class="docutils literal"><span class="pre">make</span> <span class="pre">ugly</span></code> command can be used instead of <code class="docutils literal"><span class="pre">make</span></code> to create &#8220;minified&#8221;
version (using/installing <a class="reference external" href="https://github.com/mishoo/UglifyJS">uglifyjs</a>, about 25% smaller in size).</p>
<p>Resulting &#8220;zerobinpaste&#8221; script requires only node.js (&#8220;node&#8221; binary) installed
to run and can be placed in any of the PATH dirs (e.g. &#8220;~/bin&#8221;,
&#8220;/usr/local/bin&#8221;) to be run just as &#8220;zerobinpaste&#8221;.</p>
</div>
<div class="section" id="why-node-js-and-not-python">
<h2>Why node.js and not python<a class="headerlink" href="#why-node-js-and-not-python" title="Permalink to this headline"></a></h2>
<p>Unfortunately, it&#8217;s fairly complex and unreliable to replicate non-trivial and
undocumented encryption protocol that <a class="reference external" href="http://crypto.stanford.edu/sjcl/">SJCL</a> convenience methods employ, and any
mistake in encryption is guaranteed to produce unreadable paste.</p>
<p>Current implementation uses same JavaScript code (and V8 node.js engine) that
browsers do, hence can be fairly simple and robust.</p>
<p>Future development plans include supporting configurable, less complex and more
widespread encryption schemas, allowing for simplier non-javascript client as
well.</p>
<p>See <a class="reference external" href="https://github.com/sametmax/0bin/pull/39">related pull request</a> for more details.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">zerobinpaste command-line tool</a><ul>
<li><a class="reference internal" href="#usage">Usage</a></li>
<li><a class="reference internal" href="#build-installation">Build / Installation</a></li>
<li><a class="reference internal" href="#why-node-js-and-not-python">Why node.js and not python</a></li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="options.html"
title="previous chapter">Options</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="../fr/intro.html"
title="next chapter">Introduction</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/en/zerobinpaste_tool.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../fr/intro.html" title="Introduction"
>next</a> |</li>
<li class="right" >
<a href="options.html" title="Options"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Installation avec Apache &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Installation avec Nginx" href="nginx_install.html" />
<link rel="prev" title="Installation la plus simple" href="easy_install.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="easy_install.html" title="Installation la plus simple"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="installation-avec-apache">
<h1>Installation avec Apache<a class="headerlink" href="#installation-avec-apache" title="Permalink to this headline"></a></h1>
@ -73,7 +71,7 @@ du nombre de requêtes);</li>
<li>de la solidité d&#8217;Apache en front end: il est sécurité, et il y a peu de chance
qu&#8217;il crash sous une forte charge;</li>
<li>les processus de votre site ne tournent pas avec les droits admin, même si
<a class="reference internal" href="options.html#user-and-group-fr"><em>&#8211;user</em></a> ne fonctionne pas sur votre OS.</li>
<a class="reference internal" href="options.html#user-and-group-fr"><span>&#8211;user</span></a> ne fonctionne pas sur votre OS.</li>
</ul>
<p>Apache s&#8217;installe avec votre gestionnaire de paquet habituel, nous ne couvrierons
pas cette partie.</p>
@ -82,17 +80,18 @@ pas cette partie.</p>
<p>Les serveurs Web Python modernes fonctionnent tous de la même manière, en suivant
une norme d&#8217;interfaçage: WSGI.</p>
<p>C&#8217;est la solution la plus performante, et celle recommandée. Mais elle demande
l&#8217;installation du modle Apache mod_wsgi. Si vous ne savez pas comment faire,
ou si vous ne pouvez pas le faire (par exemple sur un hébergement mutualisé
l&#8217;installation du model Apache mod_wsgi. Si vous ne savez pas comment faire,
ou si vous ne pouvez pas le faire (par example sur un hébergement mutualisé
qui ne le propose pas), il vous faudra choisir l&#8217;installation CGI.</p>
<p>Premièrement, assurez-vous d&#8217;avoir mod_wsgi installé et chargé (en tant qu&#8217;admin):</p>
<div class="highlight-python"><pre>a2enmod wsgi</pre>
<div class="highlight-python"><div class="highlight"><pre>a2enmod wsgi
</pre></div>
</div>
<p>Ceci va activer mod_wsgi. Si cela ne marche pas, il faudra l&#8217;installer d&#8217;abord (
sur ubuntu, le paquet est libapache2-mod-wsgi)</p>
<p>Ensuite, il faut créer un fichier de configuration Apache, généralement dans
/etc/apache/sites-available/. Nommez le zerobin:</p>
<div class="highlight-python"><pre>&lt;VirtualHost *:80&gt;
<div class="highlight-python"><div class="highlight"><pre>&lt;VirtualHost *:80&gt;
ServerName www.votersiteweb.com
WSGIDaemonProcess zerobin user=www-data group=www-data processes=1 threads=5
@ -104,13 +103,16 @@ sur ubuntu, le paquet est libapache2-mod-wsgi)</p>
Order deny,allow
Allow from all
&lt;/Directory&gt;
&lt;/VirtualHost&gt;</pre>
&lt;/VirtualHost&gt;
</pre></div>
</div>
<p>Activez le site web (en tant qu&#8217;admin):</p>
<div class="highlight-python"><pre>a2ensite zerobin</pre>
<div class="highlight-python"><div class="highlight"><pre>a2ensite zerobin
</pre></div>
</div>
<p>Et rechargez la configuration d&#8217;Apache (en tant qu&#8217;admin):</p>
<div class="highlight-python"><pre>service apache2 reload</pre>
<div class="highlight-python"><div class="highlight"><pre>service apache2 reload
</pre></div>
</div>
<p>Vous aurez noté que l&#8217;on fait référence à un fichier nommé app.wsgi. C&#8217;est un
fichier Python qui créé l&#8217;application qu&#8217;Apache va utiliser pour lancer le
@ -150,7 +152,7 @@ couvrir cette partie. Contactez nous si vous avez besoin de l&#8217;utiliser.</p
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -167,12 +169,14 @@ couvrir cette partie. Contactez nous si vous avez besoin de l&#8217;utiliser.</p
<h4>Next topic</h4>
<p class="topless"><a href="nginx_install.html"
title="next chapter">Installation avec Nginx</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/apache_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/apache_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -189,7 +193,7 @@ couvrir cette partie. Contactez nous si vous avez besoin de l&#8217;utiliser.</p
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -201,12 +205,12 @@ couvrir cette partie. Contactez nous si vous avez besoin de l&#8217;utiliser.</p
<li class="right" >
<a href="easy_install.html" title="Installation la plus simple"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Installation la plus simple &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Installation avec Apache" href="apache_install.html" />
<link rel="prev" title="Introduction" href="intro.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="intro.html" title="Introduction"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="installation-la-plus-simple">
<h1>Installation la plus simple<a class="headerlink" href="#installation-la-plus-simple" title="Permalink to this headline"></a></h1>
@ -59,28 +57,31 @@ Pour des solutions plus solides et sécurisées, essayez les installation
<div class="section" id="installation-en-10-secondes-si-vous-connaissez-python">
<h2>Installation en 10 secondes (si vous connaissez Python)<a class="headerlink" href="#installation-en-10-secondes-si-vous-connaissez-python" title="Permalink to this headline"></a></h2>
<p>Entrez:</p>
<div class="highlight-python"><pre>pip install zerobin
zerobin --host 0.0.0.0 --port 80 --compressed-static # en tant qu'admin</pre>
<div class="highlight-python"><div class="highlight"><pre>pip install zerobin
zerobin --host 0.0.0.0 --port 80 --compressed-static # en tant qu&#39;admin
</pre></div>
</div>
</div>
<div class="section" id="installation-en-30-secondes-pour-tous">
<h2>Installation en 30 secondes (pour tous)<a class="headerlink" href="#installation-en-30-secondes-pour-tous" title="Permalink to this headline"></a></h2>
<ul>
<li><p class="first">Assurez-vous d&#8217;avoir Python 2.6 ou 2.7 (<cite>python &#8211;version</cite>)</p>
<li><p class="first">Assurez-vous d&#8217;avoir Python 2.7 ou 3.4+ (<cite>python &#8211;version</cite>)</p>
</li>
<li><p class="first">Télécharger le dernier zip du code source.</p>
<li><p class="first">Télécharger le dernier <a class="reference external" href="https://github.com/sametmax/0bin/zipball/master">zip du code source</a>.</p>
</li>
<li><p class="first">Décompressez tous les fichiers là où vous souhaitez mettre le site.</p>
</li>
<li><p class="first">Allez dans les dossiers extraits.</p>
</li>
<li><p class="first">Lancez avec les droits admin:</p>
<div class="highlight-python"><pre>python zerobin.py --host 0.0.0.0 --port 80 --compressed-static</pre>
<div class="highlight-python"><div class="highlight"><pre>python zerobin.py --host 0.0.0.0 --port 80 --compressed-static
</pre></div>
</div>
</li>
</ul>
<p>Sous ubuntu, une line suffit:</p>
<div class="highlight-python"><pre>wget stuff &amp;&amp; unzip zerobin.zip &amp;&amp; cd zerobin &amp;&amp; sudo python zerobin.py --host 0.0.0.0 --port 80 --compressed-static</pre>
<div class="highlight-python"><div class="highlight"><pre>wget stuff &amp;&amp; unzip zerobin.zip &amp;&amp; cd zerobin &amp;&amp; sudo python zerobin.py --host 0.0.0.0 --port 80 --compressed-static
</pre></div>
</div>
<p>Jetez un oeil aux <a class="reference internal" href="options.html"><em>options de configuration</em></a>.</p>
</div>
@ -89,8 +90,9 @@ zerobin --host 0.0.0.0 --port 80 --compressed-static # en tant qu'admin</pre>
<p>0bin ne vient pas avec un moyen intégré pour le faire. Il y a plusieurs
solutions.</p>
<p><em>Pour un petit site:</em></p>
<p>Lancer simplement 0bin en processus shell d&#8217;arrière plan. Exemple sous GNU/Linux:</p>
<div class="highlight-python"><pre>nohup python zerobin.py --host 0.0.0.0 --port 80 --compressed-static &amp;</pre>
<p>Lancer simplement 0bin en processus shell d&#8217;arrière plan. Example sous GNU/Linux:</p>
<div class="highlight-python"><div class="highlight"><pre>nohup python zerobin.py --host 0.0.0.0 --port 80 --compressed-static &amp;
</pre></div>
</div>
<p>Ou dans un screen.</p>
<p><em>Pour les gros sites Web:</em></p>
@ -102,7 +104,8 @@ solutions.</p>
<p class="first admonition-title">Note</p>
<p>Vous pouvez même utiliser zerobin sur votre réseau local depuis votre portable.</p>
<p>Assurez vous que votre parefeu ne bloque pas le port, et lancez:</p>
<div class="highlight-python"><pre>python zerobin.py --host 0.0.0.0 --port 8000</pre>
<div class="highlight-python"><div class="highlight"><pre>python zerobin.py --host 0.0.0.0 --port 8000
</pre></div>
</div>
<p>0bin sera maintenant accessible sur <a class="reference external" href="http://votre.addresse.ip.locale:8000">http://votre.addresse.ip.locale:8000</a>.</p>
<p class="last">Ça peut être très un moyen très cool pour partager du code dans une entreprise
@ -115,7 +118,7 @@ ou a un code sprint.</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -133,12 +136,14 @@ ou a un code sprint.</p>
<h4>Next topic</h4>
<p class="topless"><a href="apache_install.html"
title="next chapter">Installation avec Apache</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/easy_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/easy_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -155,7 +160,7 @@ ou a un code sprint.</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -167,12 +172,12 @@ ou a un code sprint.</p>
<li class="right" >
<a href="intro.html" title="Introduction"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Introduction &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -27,10 +25,10 @@
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="0bin 0.1 documentation" href="../index.html" />
<link rel="next" title="Installation la plus simple" href="easy_install.html" />
<link rel="prev" title="Options" href="../en/options.html" />
<link rel="prev" title="zerobinpaste command-line tool" href="../en/zerobinpaste_tool.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -40,16 +38,16 @@
<a href="easy_install.html" title="Installation la plus simple"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="../en/options.html" title="Options"
<a href="../en/zerobinpaste_tool.html" title="zerobinpaste command-line tool"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="introduction">
<h1>Introduction<a class="headerlink" href="#introduction" title="Permalink to this headline"></a></h1>
@ -145,7 +143,7 @@ rapport qualité/prix.</li>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -160,17 +158,19 @@ rapport qualité/prix.</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="../en/options.html"
title="previous chapter">Options</a></p>
<p class="topless"><a href="../en/zerobinpaste_tool.html"
title="previous chapter">zerobinpaste command-line tool</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="easy_install.html"
title="next chapter">Installation la plus simple</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/intro.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/intro.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -187,7 +187,7 @@ rapport qualité/prix.</li>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -197,14 +197,14 @@ rapport qualité/prix.</li>
<a href="easy_install.html" title="Installation la plus simple"
>next</a> |</li>
<li class="right" >
<a href="../en/options.html" title="Options"
<a href="../en/zerobinpaste_tool.html" title="zerobinpaste command-line tool"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Installation avec Nginx &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Utiliser supervisor" href="using_supervisor.html" />
<link rel="prev" title="Installation avec Apache" href="apache_install.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="apache_install.html" title="Installation avec Apache"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="installation-avec-nginx">
<h1>Installation avec Nginx<a class="headerlink" href="#installation-avec-nginx" title="Permalink to this headline"></a></h1>
@ -89,7 +87,8 @@ processus. C&#8217;est très pratique pour les mises à jour.</li>
<h2>Processus Python<a class="headerlink" href="#processus-python" title="Permalink to this headline"></a></h2>
<p>Lancez 0bin comme d&#8217;habitude, mais cette fois pour écouter sur un host et un port
local. Ex</p>
<div class="highlight-python"><pre>zerobin --host 127.0.0.1 --port 8000</pre>
<div class="highlight-python"><div class="highlight"><pre>zerobin --host 127.0.0.1 --port 8000
</pre></div>
</div>
<p>En PHP, quand on édite un fichier, la modificiation est visible immédiatement.
En Python, l&#8217;intégralité du code est chargé en mémoire pour des raisons de
@ -104,19 +103,20 @@ nous ne couvrirons pas cette partie.</p>
<p>Vous devez créer une fichier de configuration Nginx pour 0bin. Sous GNU/Linux,
on les mets en général dans /etc/nginx/conf.d/. Nommez le zerobin.conf.</p>
<p>Le fichier de configuration minimal pour faire tourner le site est:</p>
<div class="highlight-python"><pre>server {
<div class="highlight-python"><div class="highlight"><pre>server {
listen 80;
server_name www.votresiteweb.com;
location / {
proxy_pass http://127.0.0.1:8000;
}
}</pre>
}
</pre></div>
</div>
<p><cite>proxy_pass</cite> transmet les requêtes aux processus Python. Bien entendu le
port doit correspondre à celui utilisé par 0bin.</p>
<p>On peut apporter plusieurs améliorations à l&#8217;expérience utilisateur:</p>
<div class="highlight-python"><pre>server {
<div class="highlight-python"><div class="highlight"><pre>server {
listen 80;
server_name www.votresiteweb.com;
@ -141,7 +141,8 @@ port doit correspondre à celui utilisé par 0bin.</p>
location / {
proxy_pass http://zerobin_cherrypy;
}
}</pre>
}
</pre></div>
</div>
<p>Nginx sert maintenant le favicon ainsi que les fichiers statiques,
on a ajouté une date d&#8217;expiration dans les en-têtes HTTP
@ -154,7 +155,7 @@ qui la supporte.</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -171,12 +172,14 @@ qui la supporte.</p>
<h4>Next topic</h4>
<p class="topless"><a href="using_supervisor.html"
title="next chapter">Utiliser supervisor</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/nginx_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/nginx_install.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -193,7 +196,7 @@ qui la supporte.</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -205,12 +208,12 @@ qui la supporte.</p>
<li class="right" >
<a href="apache_install.html" title="Installation avec Apache"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Options &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -26,26 +24,30 @@
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="0bin 0.1 documentation" href="../index.html" />
<link rel="next" title="Outil en ligne de commande zerobinpaste" href="zerobinpaste_tool.html" />
<link rel="prev" title="Personnaliser lapparence" href="theming.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="zerobinpaste_tool.html" title="Outil en ligne de commande zerobinpaste"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="theming.html" title="Personnaliser lapparence"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="options">
<h1>Options<a class="headerlink" href="#options" title="Permalink to this headline"></a></h1>
@ -105,7 +107,8 @@ production pour un site plus rapide.</p>
<div class="section" id="exemples">
<h3>Exemples<a class="headerlink" href="#exemples" title="Permalink to this headline"></a></h3>
<p>Production:</p>
<div class="highlight-python"><pre>sudo zerobin --host 0.0.0.0 --port 80 --user foo --compressed-static</pre>
<div class="highlight-python"><div class="highlight"><pre>sudo zerobin --host 0.0.0.0 --port 80 --user foo --compressed-static
</pre></div>
</div>
<p>Développement:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="n">zerobin</span> <span class="o">--</span><span class="n">debug</span> <span class="o">--</span><span class="n">serve</span><span class="o">-</span><span class="n">static</span>
@ -117,7 +120,8 @@ production pour un site plus rapide.</p>
<h2>Fichier de configuration<a class="headerlink" href="#fichier-de-configuration" title="Permalink to this headline"></a></h2>
<p>Le fichier configuration doit être un fichier Python ordinnaire, généralement
appelé settings.py. On l&#8217;utilise ainsi:</p>
<div class="highlight-python"><pre>zerobin --settings-file '/chemin/vers/settings.py'</pre>
<div class="highlight-python"><div class="highlight"><pre>zerobin --settings-file &#39;/chemin/vers/settings.py&#39;
</pre></div>
</div>
<p>Toutes les autres options passées à la commandes auront priorité sur les options
du fichier de configuration. Le fichier zerobin/default_settings.py peut servir
@ -215,7 +219,7 @@ est en haut de chaque page.</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
@ -249,12 +253,17 @@ est en haut de chaque page.</p>
<h4>Previous topic</h4>
<p class="topless"><a href="theming.html"
title="previous chapter">Personnaliser l&#8217;apparence</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/options.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<h4>Next topic</h4>
<p class="topless"><a href="zerobinpaste_tool.html"
title="next chapter">Outil en ligne de commande zerobinpaste</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/options.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -271,21 +280,24 @@ est en haut de chaque page.</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="zerobinpaste_tool.html" title="Outil en ligne de commande zerobinpaste"
>next</a> |</li>
<li class="right" >
<a href="theming.html" title="Personnaliser lapparence"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Personnaliser lapparence &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Options" href="options.html" />
<link rel="prev" title="Utiliser supervisor" href="using_supervisor.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="using_supervisor.html" title="Utiliser supervisor"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="personnaliser-l-apparence">
<h1>Personnaliser l&#8217;apparence<a class="headerlink" href="#personnaliser-l-apparence" title="Permalink to this headline"></a></h1>
@ -57,10 +55,10 @@
<p>Si vous souhaitez créer votre propre theme, il vous faut créer des templates
similaires à ceux par défault présents dans zerobin/view, et
ajouter le chemin du dossier contenant ces templates au
<a class="reference internal" href="options.html#template-dirs-fr"><em>fichier de configuration</em></a>.</p>
<a class="reference internal" href="options.html#template-dirs-fr"><span>fichier de configuration</span></a>.</p>
<p>Vous aurez également besoin de copier les fichiers statiques présent
dans zerobin/static dans un nouveau dossier, puis les modifier. Et signifiez
que vous utilisez ce dossier dans le <a class="reference internal" href="options.html#static-root-fr"><em>fichier de configuration</em></a>.</p>
que vous utilisez ce dossier dans le <a class="reference internal" href="options.html#static-root-fr"><span>fichier de configuration</span></a>.</p>
<p>Vous pouvez bien entendu également éditez tous les fichier directement par
souci de simplicité.</p>
<p>Attention cependant, le code javascript est très dépendant des ID et classes
@ -73,7 +71,7 @@ améliorons le support.</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h4>Previous topic</h4>
<p class="topless"><a href="using_supervisor.html"
@ -81,12 +79,14 @@ améliorons le support.</p>
<h4>Next topic</h4>
<p class="topless"><a href="options.html"
title="next chapter">Options</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/theming.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/theming.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -103,7 +103,7 @@ améliorons le support.</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -115,12 +115,12 @@ améliorons le support.</p>
<li class="right" >
<a href="using_supervisor.html" title="Utiliser supervisor"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,7 +8,7 @@
<title>Utiliser supervisor &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
@ -29,8 +27,8 @@
<link rel="next" title="Personnaliser lapparence" href="theming.html" />
<link rel="prev" title="Installation avec Nginx" href="nginx_install.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -42,14 +40,14 @@
<li class="right" >
<a href="nginx_install.html" title="Installation avec Nginx"
accesskey="P">previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="utiliser-supervisor">
<h1>Utiliser supervisor<a class="headerlink" href="#utiliser-supervisor" title="Permalink to this headline"></a></h1>
@ -58,7 +56,7 @@ pas couvrir son installation (qui la plupart du temps se résume à
apt-get install supervisor ou pip install supervisor), mais voici un rapide
résumé de comment l&#8217;utiliser:</p>
<p>Créez un fichier de configuration nommé supervisor.ini:</p>
<div class="highlight-python"><pre>[unix_http_server]
<div class="highlight-python"><div class="highlight"><pre>[unix_http_server]
file=/tmp/supervisor.sock;
[supervisorctl]
@ -81,17 +79,19 @@ user=zerobin
[program:zerobin]
command=/chemin/vers/zerobin/zerobin.py --port 80 --compressed-static
directory=/chemin/vers/zerobin/
environment=PYTHONPATH='/chemin/vers/zerobin/'
environment=PYTHONPATH=&#39;/chemin/vers/zerobin/&#39;
user=zerobin
autostart=true
autorestart=true</pre>
autorestart=true
</pre></div>
</div>
<p>Les 4 premières entrées sont juste de la configuration standard et vous pouvez
les copier telles qu&#8217;elles.</p>
<p>La dernière entrée définie un processus (il peut y en avoir plusieurs)
que supervisor doit gérer.</p>
<p>Cela veut dire qu&#8217;il va lancer la commande:</p>
<div class="highlight-python"><pre>/chemin/vers/zerobin/zerobin.py --port 80 --compressed-static</pre>
<div class="highlight-python"><div class="highlight"><pre>/chemin/vers/zerobin/zerobin.py --port 80 --compressed-static
</pre></div>
</div>
<p>Et ceci dans le dossier, avec l&#8217;environnement et l&#8217;utilisateur défini, le tout
en arrière plan en tant que daemon.</p>
@ -112,7 +112,8 @@ sur le service.</p>
<p class="first admonition-title">Note</p>
<p>Si vous avez installé zerobin dans un virtualenv, vous devriez définir la
commande pour qu&#8217;elle s&#8217;éxécute depuis le virtualenv:</p>
<div class="last highlight-python"><pre>command=/chemin/vers/le/virtualenv/bin/zerobin --port 80 --compressed-static</pre>
<div class="last highlight-python"><div class="highlight"><pre>command=/chemin/vers/le/virtualenv/bin/zerobin --port 80 --compressed-static
</pre></div>
</div>
</div>
</div>
@ -121,7 +122,7 @@ commande pour qu&#8217;elle s&#8217;éxécute depuis le virtualenv:</p>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h4>Previous topic</h4>
<p class="topless"><a href="nginx_install.html"
@ -129,12 +130,14 @@ commande pour qu&#8217;elle s&#8217;éxécute depuis le virtualenv:</p>
<h4>Next topic</h4>
<p class="topless"><a href="theming.html"
title="next chapter">Personnaliser l&#8217;apparence</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/using_supervisor.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/using_supervisor.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
@ -151,7 +154,7 @@ commande pour qu&#8217;elle s&#8217;éxécute depuis le virtualenv:</p>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -163,12 +166,12 @@ commande pour qu&#8217;elle s&#8217;éxécute depuis le virtualenv:</p>
<li class="right" >
<a href="nginx_install.html" title="Installation avec Nginx"
>previous</a> |</li>
<li><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -0,0 +1,181 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Outil en ligne de commande zerobinpaste &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '../',
VERSION: '0.1',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="../_static/jquery.js"></script>
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="0bin 0.1 documentation" href="../index.html" />
<link rel="prev" title="Options" href="options.html" />
</head>
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="options.html" title="Options"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="outil-en-ligne-de-commande-zerobinpaste">
<h1>Outil en ligne de commande zerobinpaste<a class="headerlink" href="#outil-en-ligne-de-commande-zerobinpaste" title="Permalink to this headline"></a></h1>
<p>zerobinpaste est un simple outil CLI (similaire à pastebinit ou wgetpaste) à utiliser
avec des fichiers ou des redirection shell dans le terminal ou des scripts simples.</p>
<p>Exemple de cas d&#8217;utilisation:</p>
<div class="highlight-python"><div class="highlight"><pre>% zerobinpaste README.rst
http://some.0bin.site/paste/0cc3d8a8...
% grep error /var/log/syslog | zerobinpaste
http://some.0bin.site/paste/81fd1324...
% zerobinpaste docs/en/*.rst
easy_install.rst http://some.0bin.site/paste/9adc576a...
apache_install.rst http://some.0bin.site/paste/01408cbd...
options.rst http://some.0bin.site/paste/921b2768...
...
% ps axlf | zerobinpaste | mail -s &quot;Process tree on $(date)&quot; root
</pre></div>
</div>
<p>Les liens produits peuvent être ensuite copier-coller sur un channel IRC
ou autre.</p>
<p>L&#8217;outil produit le chiffrement lui-même sur la machine et la clé (après le hash)
n&#8217;est jamais envoyée au serveur ou nul par ailleurs excepté la sortie standard
de l&#8217;outil (ex: le terminal).</p>
<p>L&#8217;outil doit être buildé avec <a class="reference external" href="http://nodejs.org/">node.js</a> séparément (voir plus bas).</p>
<div class="section" id="usage">
<h2>Usage<a class="headerlink" href="#usage" title="Permalink to this headline"></a></h2>
<p>Au minimum il faut préciser le site pastebin (l&#8217;url principal d&#8217;où on posterait
dans le navigateur) doit être spécifié à l&#8217;outil via l&#8217;option -u (&#8211;url) (on peut
le simplifier avec un alias shell - ex: <code class="docutils literal"><span class="pre">alias</span> <span class="pre">zp='zerobinpaste</span> <span class="pre">-u</span> <span class="pre">http://some.0bin.site</span></code>)
ou dans le fichier de configuration &#8220;~/.zerobinpasterc&#8221; (format json).</p>
<div class="line-block">
<div class="line">Les arguments positionels sont interprétés comme des fichiers à uploader et chiffrer.</div>
<div class="line">Si aucun argument n&#8217;est passé, le script tentera de lire stdin.</div>
</div>
<p>Le fichier de configuration le plus simple pourrait ressembler à celà:</p>
<blockquote>
<div>{&#8220;url&#8221;: &#8220;<a class="reference external" href="http://some.0bin.site">http://some.0bin.site</a>&#8220;}</div></blockquote>
<p>Toute option (dans sa forme longue, ex: &#8220;url pour &#8211;url) utilisable en ligne de commande
peut être spécifié ici.</p>
<p>Lancez l&#8217;outil avec -h ou &#8211;help pour voir la liste des paramètres supportés.</p>
</div>
<div class="section" id="build-installation">
<h2>Build / Installation<a class="headerlink" href="#build-installation" title="Permalink to this headline"></a></h2>
<p>En bref:</p>
<blockquote>
<div>0bin% cd tools
0bin/tools% make
...
0bin/tools% cp zerobinpaste ~/bin # install to PATH</div></blockquote>
<p>La commande &#8220;npm&#8221; (packagé and installé avec node.js) est requise pour télécharger
les dépendances indispensables à la production de l&#8217;éxécutable.</p>
<p>Utilisez &#8220;make&#8221; dans le dossier &#8220;tools&#8221; pour produire une version non-minifié&#8221; de &#8220;zerobinpaste&#8221;.</p>
<p>La commande <code class="docutils literal"><span class="pre">make</span> <span class="pre">ugly</span></code> peut être utilisé à la place de <code class="docutils literal"><span class="pre">make</span></code> pour créer une version &#8220;minified&#8221;
(requière l&#8217;installation de <a class="reference external" href="https://github.com/mishoo/UglifyJS">uglifyjs</a>, script produit environ 25% plus petit en taille).</p>
<p>Le script &#8220;zerobinpaste&#8221; ainsi produit seulement besoin de node.js (et la commande &#8220;node&#8221;) pour
s&#8217;éxcuter et peut être placé n&#8217;importe où dans le PATH système (ex : &#8220;~/bin&#8221;, &#8220;/usr/local/bin&#8221;)
afin d&#8217;être exécuté en tapant simplement &#8220;zerobinpaste&#8221;.</p>
</div>
<div class="section" id="pourquoi-node-js-et-pas-python">
<h2>Pourquoi node.js et pas python<a class="headerlink" href="#pourquoi-node-js-et-pas-python" title="Permalink to this headline"></a></h2>
<p>Malheureusement, il est assez difficile et peu fiable de répliquer un protocole
chiffrement non trivial et non documenté tel que celui de certaines méthodes
de <a class="reference external" href="http://crypto.stanford.edu/sjcl/">SJCL</a>, et la moindre erreur garantie de produire une paste illisible.</p>
<p>L&#8217;implémentation actuelle utilise le même code Javascript (via le moteur V8 de node.js)
que le navigateur, du coup il est simple et robuste.</p>
<p>Il est prévu de supporter plus tard un schéma de chiffrement plus configurable, moins
complexe et plus courant, permettant à des clients non-javascript de fonctioner également.</p>
<p>Voir le <a class="reference external" href="https://github.com/sametmax/0bin/pull/39">pull request concerné</a> pour plus de détails.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../index.html">Table Of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">Outil en ligne de commande zerobinpaste</a><ul>
<li><a class="reference internal" href="#usage">Usage</a></li>
<li><a class="reference internal" href="#build-installation">Build / Installation</a></li>
<li><a class="reference internal" href="#pourquoi-node-js-et-pas-python">Pourquoi node.js et pas python</a></li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="options.html"
title="previous chapter">Options</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../_sources/fr/zerobinpaste_tool.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="options.html" title="Options"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="../index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,7 +1,4 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -12,12 +9,12 @@
<title>Index &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/classic.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '',
URL_ROOT: './',
VERSION: '0.1',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
@ -29,21 +26,21 @@
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="top" title="0bin 0.1 documentation" href="index.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
accesskey="I">index</a></li>
<li><a href="index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<h1 id="index">Index</h1>
@ -56,12 +53,12 @@
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<div id="searchbox" style="display: none">
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<input type="text" name="q" />
@ -78,18 +75,18 @@
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
>index</a></li>
<li><a href="index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,12 +8,12 @@
<title>0bins documentation &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/classic.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '',
URL_ROOT: './',
VERSION: '0.1',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
@ -28,8 +26,8 @@
<link rel="top" title="0bin 0.1 documentation" href="#" />
<link rel="next" title="Introduction" href="en/intro.html" />
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -38,14 +36,14 @@
<li class="right" >
<a href="en/intro.html" title="Introduction"
accesskey="N">next</a> |</li>
<li><a href="#">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="#">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<div class="section" id="bin-s-documentation">
<h1>0bin&#8217;s documentation<a class="headerlink" href="#bin-s-documentation" title="Permalink to this headline"></a></h1>
@ -72,9 +70,10 @@
<li class="toctree-l1"><a class="reference internal" href="en/using_supervisor.html">Using supervisor</a></li>
<li class="toctree-l1"><a class="reference internal" href="en/theming.html">Theming</a></li>
<li class="toctree-l1"><a class="reference internal" href="en/options.html">Options</a></li>
<li class="toctree-l1"><a class="reference internal" href="en/zerobinpaste_tool.html">zerobinpaste command-line tool</a></li>
</ul>
</div>
<p class="last"><a class="reference external" href="&lt;https://github.com/sametmax/0bin/issues&gt;">Report a bug</a></p>
<p class="last"><a class="reference external" href="https://github.com/sametmax/0bin/issues">Report a bug</a></p>
</td>
<td><div class="toctree-wrapper first compound">
<ul>
@ -85,6 +84,7 @@
<li class="toctree-l1"><a class="reference internal" href="fr/using_supervisor.html">Utiliser supervisor</a></li>
<li class="toctree-l1"><a class="reference internal" href="fr/theming.html">Personnaliser l&#8217;apparence</a></li>
<li class="toctree-l1"><a class="reference internal" href="fr/options.html">Options</a></li>
<li class="toctree-l1"><a class="reference internal" href="fr/zerobinpaste_tool.html">Outil en ligne de commande zerobinpaste</a></li>
</ul>
</div>
<p class="last"><a class="reference external" href="https://github.com/sametmax/0bin/issues">Signaler un bug</a></p>
@ -98,17 +98,19 @@
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h4>Next topic</h4>
<p class="topless"><a href="en/intro.html"
title="next chapter">Introduction</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="_sources/index.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none">
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="_sources/index.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<input type="text" name="q" />
@ -125,7 +127,7 @@
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
@ -134,12 +136,12 @@
<li class="right" >
<a href="en/intro.html" title="Introduction"
>next</a> |</li>
<li><a href="#">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="#">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

Binary file not shown.

View File

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -10,12 +8,12 @@
<title>Search &mdash; 0bin 0.1 documentation</title>
<link rel="stylesheet" href="_static/default.css" type="text/css" />
<link rel="stylesheet" href="_static/classic.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '',
URL_ROOT: './',
VERSION: '0.1',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
@ -30,24 +28,26 @@
<script type="text/javascript">
jQuery(function() { Search.loadIndex("searchindex.js"); });
</script>
<script type="text/javascript" id="searchindexloader"></script>
</head>
<body>
<div class="related">
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li><a href="index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<div class="body" role="main">
<h1 id="search-documentation">Search</h1>
<div id="fallback" class="admonition warning">
@ -76,24 +76,24 @@
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li><a href="index.html">0bin 0.1 documentation</a> &raquo;</li>
<li class="nav-item nav-item-0"><a href="index.html">0bin 0.1 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
<div class="footer" role="contentinfo">
&copy; Copyright 2012, Sam et Max.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.1.3.
Created using <a href="http://sphinx-doc.org/">Sphinx</a> 1.3.1.
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ Just type::
30 seconds setup (for anybody)
===============================
- Make sure you have Python 2.6 or 2.7 (`python --version`)
- Make sure you have Python 2.7 or 3.4+ (`python --version`)
- Download the last `zip of the source code <https://github.com/sametmax/0bin/zipball/master>`_
- Extract all of it where you wish the site to be stored.
- Go to the extracted files.
@ -59,4 +59,4 @@ Or run it in a screen.
0bin will now be accessible from http://your.local.ip.address:8000.
This can be very cool way to share code in a companie or during a code sprint.
This can be very cool way to share code in a companie or during a code sprint.

View File

@ -45,7 +45,8 @@ Other features
- copy paste to clipboard in a click;
- get paste short URL in a click;
- own previous pastes history;
- visual hash of a paste to easily tell it apart from others in a list.
- visual hash of a paste to easily tell it apart from others in a list;
- `optional command-line tool`_ to encrypt and paste data from shell or scripts.
Technologies used
==================
@ -57,6 +58,7 @@ Technologies used
- Bootstrap_, the Twitter HTML5/CSS3 framework
- VizHash.js_ to create visual hashes from pastes
- Cherrypy_ (server only)
- `node.js`_ (for optional command-line tool only)
Known issues
@ -84,4 +86,6 @@ What does 0bin not implement?
.. _Bootstrap: http://twitter.github.com/bootstrap/
.. _VizHash.js: https://github.com/sametmax/VizHash.js
.. _Cherrypy: http://www.cherrypy.org/ (server only)
.. _node.js: http://nodejs.org/
.. _optional command-line tool: ./zerobinpaste_tool
.. _is not worth it: http://stackoverflow.com/questions/201705/how-many-random-elements-before-md5-produces-collisions

View File

@ -0,0 +1,100 @@
==============================
zerobinpaste command-line tool
==============================
zerobinpaste is a simple CLI tool (analogous to pastebinit or wgetpaste) to use
with files or shell redirection in terminal or simple scripts.
Example use-cases might look like::
% zerobinpaste README.rst
http://some.0bin.site/paste/0cc3d8a8...
% grep error /var/log/syslog | zerobinpaste
http://some.0bin.site/paste/81fd1324...
% zerobinpaste docs/en/*.rst
easy_install.rst http://some.0bin.site/paste/9adc576a...
apache_install.rst http://some.0bin.site/paste/01408cbd...
options.rst http://some.0bin.site/paste/921b2768...
...
% ps axlf | zerobinpaste | mail -s "Process tree on $(date)" root
Produced links can then be copy-pasted to some IRC channel or used in whatever
other conceivable way.
Tool does encryption by itself on the client machine and key (after hashmark) is
never sent to server or anywhere but the tool's stdout stream (e.g. terminal).
Tool has to be built with `node.js`_ separately (see below).
Usage
=====
At least the pastebin site (main URL where you'd paste stuff with the browser)
has to be specified for the tool to use either via -u (--url) option (can be
simplified with shell alias - e.g. ``alias zp='zerobinpaste -u http://some.0bin.site``)
or in the "~/.zerobinpasterc" configuration file (json format).
| Non-option arguments are interpreted as files to upload/paste contents of.
| If no arguments are specified, data to paste will be read from stdin stream.
Simple configuration file may look like this:
{"url": "http://some.0bin.site"}
Any options (in the long form, e.g. "url" for --url above) that are allowed on
the command-line can be specified there.
Run the tool with -h or --help option to see full list of supported parameters.
Build / Installation
====================
In essence:
0bin% cd tools
0bin/tools% make
...
0bin/tools% cp zerobinpaste ~/bin # install to PATH
"npm" binary (packaged and installed with node.js) is required to pull in build
dependencies, if necessary, and "node" binary is required for produced binary to
run.
Use "make" in "tools" path to produce non-minified runnable "zerobinpaste"
script there.
``make ugly`` command can be used instead of ``make`` to create "minified"
version (using/installing uglifyjs_, about 25% smaller in size).
Resulting "zerobinpaste" script requires only node.js ("node" binary) installed
to run and can be placed in any of the PATH dirs (e.g. "~/bin",
"/usr/local/bin") to be run just as "zerobinpaste".
Why node.js and not python
==========================
Unfortunately, it's fairly complex and unreliable to replicate non-trivial and
undocumented encryption protocol that SJCL_ convenience methods employ, and any
mistake in encryption is guaranteed to produce unreadable paste.
Current implementation uses same JavaScript code (and V8 node.js engine) that
browsers do, hence can be fairly simple and robust.
Future development plans include supporting configurable, less complex and more
widespread encryption schemas, allowing for simplier non-javascript client as
well.
See `related pull request`_ for more details.
.. _node.js: http://nodejs.org/
.. _uglifyjs: https://github.com/mishoo/UglifyJS
.. _SJCL: http://crypto.stanford.edu/sjcl/
.. _related pull request: https://github.com/sametmax/0bin/pull/39

View File

@ -44,10 +44,15 @@ GROUP = None
# Names/links to insert in the menu bar.
# Any link with "mailto:" will be escaped to prevent spam
MENU = (
('Home', '/'), # internal link. First link will be highlited
('Download 0bin', 'https://github.com/sametmax/0bin'), # external link
('Contact', 'mailto:your@email.com') # email
("Github", "https://github.com/Tygs/0bin"),
("Faq", "/faq/"), # You probably want to keep this
# Any link with "mailto:" will be escaped to limit spam, but displayed
# correctly to the user using JS.
("Contact", "mailto:your@email.com"),
("Zerobin Pastebin", "https://www.0bin.net/"), # Thanks the authors :)
("How to buy Bitcoin?", "/buy_bitcoin"), # Thanks the authors :)
)
# limit size of pasted text in bytes. Be careful allowing too much size can slow down user's

View File

@ -18,7 +18,7 @@ Entrez::
Installation en 30 secondes (pour tous)
=======================================
- Assurez-vous d'avoir Python 2.6 ou 2.7 (`python --version`)
- Assurez-vous d'avoir Python 2.7 ou 3.4+ (`python --version`)
- Télécharger le dernier `zip du code source <https://github.com/sametmax/0bin/zipball/master>`_.
- Décompressez tous les fichiers là où vous souhaitez mettre le site.
- Allez dans les dossiers extraits.

View File

@ -0,0 +1,96 @@
=======================================
Outil en ligne de commande zerobinpaste
=======================================
zerobinpaste est un simple outil CLI (similaire à pastebinit ou wgetpaste) à utiliser
avec des fichiers ou des redirection shell dans le terminal ou des scripts simples.
Exemple de cas d'utilisation::
% zerobinpaste README.rst
http://some.0bin.site/paste/0cc3d8a8...
% grep error /var/log/syslog | zerobinpaste
http://some.0bin.site/paste/81fd1324...
% zerobinpaste docs/en/*.rst
easy_install.rst http://some.0bin.site/paste/9adc576a...
apache_install.rst http://some.0bin.site/paste/01408cbd...
options.rst http://some.0bin.site/paste/921b2768...
...
% ps axlf | zerobinpaste | mail -s "Process tree on $(date)" root
Les liens produits peuvent être ensuite copier-coller sur un channel IRC
ou autre.
L'outil produit le chiffrement lui-même sur la machine et la clé (après le hash)
n'est jamais envoyée au serveur ou nul par ailleurs excepté la sortie standard
de l'outil (ex: le terminal).
L'outil doit être buildé avec `node.js`_ séparément (voir plus bas).
Usage
=====
Au minimum il faut préciser le site pastebin (l'url principal d'où on posterait
dans le navigateur) doit être spécifié à l'outil via l'option -u (--url) (on peut
le simplifier avec un alias shell - ex: ``alias zp='zerobinpaste -u http://some.0bin.site``)
ou dans le fichier de configuration "~/.zerobinpasterc" (format json).
| Les arguments positionels sont interprétés comme des fichiers à uploader et chiffrer.
| Si aucun argument n'est passé, le script tentera de lire stdin.
Le fichier de configuration le plus simple pourrait ressembler à celà:
{"url": "http://some.0bin.site"}
Toute option (dans sa forme longue, ex: "url pour --url) utilisable en ligne de commande
peut être spécifié ici.
Lancez l'outil avec -h ou --help pour voir la liste des paramètres supportés.
Build / Installation
====================
En bref:
0bin% cd tools
0bin/tools% make
...
0bin/tools% cp zerobinpaste ~/bin # install to PATH
La commande "npm" (packagé and installé avec node.js) est requise pour télécharger
les dépendances indispensables à la production de l'éxécutable.
Utilisez "make" dans le dossier "tools" pour produire une version non-minifié" de "zerobinpaste".
La commande ``make ugly`` peut être utilisé à la place de ``make`` pour créer une version "minified"
(requière l'installation de uglifyjs_, script produit environ 25% plus petit en taille).
Le script "zerobinpaste" ainsi produit seulement besoin de node.js (et la commande "node") pour
s'éxcuter et peut être placé n'importe où dans le PATH système (ex : "~/bin", "/usr/local/bin")
afin d'être exécuté en tapant simplement "zerobinpaste".
Pourquoi node.js et pas python
==============================
Malheureusement, il est assez difficile et peu fiable de répliquer un protocole
chiffrement non trivial et non documenté tel que celui de certaines méthodes
de SJCL_, et la moindre erreur garantie de produire une paste illisible.
L'implémentation actuelle utilise le même code Javascript (via le moteur V8 de node.js)
que le navigateur, du coup il est simple et robuste.
Il est prévu de supporter plus tard un schéma de chiffrement plus configurable, moins
complexe et plus courant, permettant à des clients non-javascript de fonctioner également.
Voir le `pull request concerné`_ pour plus de détails.
.. _node.js: http://nodejs.org/
.. _uglifyjs: https://github.com/mishoo/UglifyJS
.. _SJCL: http://crypto.stanford.edu/sjcl/
.. _pull request concerné: https://github.com/sametmax/0bin/pull/39

View File

@ -22,10 +22,11 @@
| en/using_supervisor | fr/using_supervisor |
| en/theming | fr/theming |
| en/options | fr/options |
| en/zerobinpaste_tool | fr/zerobinpaste_tool |
| | |
|`Report a bug`_ |`Signaler un bug`_ |
+-------------------------+--------------------------------+
.. _Signaler un bug: https://github.com/sametmax/0bin/issues
.. _Report a bug: https://github.com/sametmax/0bin/issues
.. _Report a bug: https://github.com/sametmax/0bin/issues

146
dodo.py Normal file
View File

@ -0,0 +1,146 @@
import sys
import subprocess
from pathlib import Path
from fnmatch import fnmatch
from doit.tools import PythonInteractiveAction
try:
from local_dodo import *
except ImportError:
pass
ROOT = Path(__file__).absolute().parent
SOURCE_DIR = ROOT / "zerobin/"
DIST_DIR = ROOT / "dist"
ZEROBIN_VERSION = (SOURCE_DIR / "VERSION").read_text().strip()
DOIT_CONFIG = {
"default_tasks": ["task_compress"],
"action_string_formatting": "new",
}
def git(*args, **kwargs):
return subprocess.check_output(
["git", *args], stderr=subprocess.STDOUT, universal_newlines=True,
).rstrip("\n")
def source_files(extensions=None, exclude=()):
exclude_filter = ["*.pyc", ".*"]
exclude_filter.extend(exclude)
extensions = extensions or [".*"]
for ext in extensions:
for file in SOURCE_DIR.rglob(f"*{ext}"):
if not (
file.is_dir()
or any(fnmatch(file, pattern) for pattern in exclude_filter)
):
yield file
def task_generate_manifest():
def generate():
globs = " ".join(set(f"*{f.suffix}" for f in source_files()))
globs += " VERSION"
(ROOT / "MANIFEST.in").write_text(f"recursive-include zerobin {globs}")
return {
"targets": [ROOT / "MANIFEST.in"],
"actions": [generate],
}
def task_compress():
main_js = str(SOURCE_DIR / "static/js/main.min.js")
main_css = str(SOURCE_DIR / "static/css/style.min.css")
return {
"targets": [main_js, main_css],
"file_dep": list(
str(f) for f in source_files([".css", ".js"], exclude=[main_css, main_js])
),
"actions": [str(ROOT / "compress.sh")],
}
def task_build():
return {
"targets": [DIST_DIR / f"zerobin-{ZEROBIN_VERSION}-py3-none-any.whl"],
"file_dep": list(str(f) for f in source_files() if ".min." not in str(f)),
"task_dep": ["compress", "generate_manifest"],
"actions": ["python setup.py bdist_wheel"],
}
def task_publish_to_pypi():
return {
"task_dep": ["build"],
"actions": ["twine upload ./dist/*.whl"],
}
def task_build_pyz():
return {
"actions": ["shiv zerobin -o dist/zerobin.pyz -c zerobin"],
}
def task_bump_version():
def bump():
if git("branch", "--show-current") != "master":
sys.exit("You must be on the branch master to do that")
git("fetch", "origin", "master")
if git("rev-parse", "@{u}") != git("merge-base", "@", "@{u}"):
sys.exit("Cannot push a new version, you need to pull first")
git_status = git("status", "--porcelain=1").split("\n")
if not all(
line.startswith(" ") for line in git_status if not line.startswith("?")
):
sys.exit("Cannot commit the new version: you have changes to be commited.")
print("Current version is:", ZEROBIN_VERSION)
action = "0"
while action not in "123":
print("What kind of version is it?\n")
print("1) Major")
print("2) Minor")
print("3) Fix")
action = input("Enter 1, 2 or 3 (Ctrl + C to quit): ")
new_version = list(map(int, ZEROBIN_VERSION.split(".")))
action = int(action) - 1
new_version[action] += 1
new_version = ".".join(map(str, new_version))
print("The new version will be:", new_version)
if input("Ok? [y/N] ").strip().lower() != "y":
sys.exit("The version has NOT been bumped")
print("- Updating VERSION file")
(SOURCE_DIR / "VERSION").write_text(new_version)
print("- Commiting VERSION file")
git("add", "zerobin/VERSION")
git(
"commit", "-m", f"Bumping to version {new_version}",
)
print(f"- Adding v{new_version} tag")
git("tag", f"v{new_version}")
print("- Pushing tag")
git("push", "origin", "master", "--tag")
return {
"actions": [PythonInteractiveAction(bump),],
}
def task_release_to_pypi():
return {"task_dep": ["bump_version", "publish_to_pypi"], "actions": []}

View File

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +0,0 @@
Copyright (c) 2004-2011, CherryPy Team (team@cherrypy.org)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the CherryPy Team nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,624 +0,0 @@
"""CherryPy is a pythonic, object-oriented HTTP framework.
CherryPy consists of not one, but four separate API layers.
The APPLICATION LAYER is the simplest. CherryPy applications are written as
a tree of classes and methods, where each branch in the tree corresponds to
a branch in the URL path. Each method is a 'page handler', which receives
GET and POST params as keyword arguments, and returns or yields the (HTML)
body of the response. The special method name 'index' is used for paths
that end in a slash, and the special method name 'default' is used to
handle multiple paths via a single handler. This layer also includes:
* the 'exposed' attribute (and cherrypy.expose)
* cherrypy.quickstart()
* _cp_config attributes
* cherrypy.tools (including cherrypy.session)
* cherrypy.url()
The ENVIRONMENT LAYER is used by developers at all levels. It provides
information about the current request and response, plus the application
and server environment, via a (default) set of top-level objects:
* cherrypy.request
* cherrypy.response
* cherrypy.engine
* cherrypy.server
* cherrypy.tree
* cherrypy.config
* cherrypy.thread_data
* cherrypy.log
* cherrypy.HTTPError, NotFound, and HTTPRedirect
* cherrypy.lib
The EXTENSION LAYER allows advanced users to construct and share their own
plugins. It consists of:
* Hook API
* Tool API
* Toolbox API
* Dispatch API
* Config Namespace API
Finally, there is the CORE LAYER, which uses the core API's to construct
the default components which are available at higher layers. You can think
of the default components as the 'reference implementation' for CherryPy.
Megaframeworks (and advanced users) may replace the default components
with customized or extended components. The core API's are:
* Application API
* Engine API
* Request API
* Server API
* WSGI API
These API's are described in the CherryPy specification:
http://www.cherrypy.org/wiki/CherryPySpec
"""
__version__ = "3.2.2"
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
from cherrypy._cpcompat import basestring, unicodestr, set
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError
from cherrypy import _cpdispatch as dispatch
from cherrypy import _cptools
tools = _cptools.default_toolbox
Tool = _cptools.Tool
from cherrypy import _cprequest
from cherrypy.lib import httputil as _httputil
from cherrypy import _cptree
tree = _cptree.Tree()
from cherrypy._cptree import Application
from cherrypy import _cpwsgi as wsgi
from cherrypy import process
try:
from cherrypy.process import win32
engine = win32.Win32Bus()
engine.console_control_handler = win32.ConsoleCtrlHandler(engine)
del win32
except ImportError:
engine = process.bus
# Timeout monitor. We add two channels to the engine
# to which cherrypy.Application will publish.
engine.listeners['before_request'] = set()
engine.listeners['after_request'] = set()
class _TimeoutMonitor(process.plugins.Monitor):
def __init__(self, bus):
self.servings = []
process.plugins.Monitor.__init__(self, bus, self.run)
def before_request(self):
self.servings.append((serving.request, serving.response))
def after_request(self):
try:
self.servings.remove((serving.request, serving.response))
except ValueError:
pass
def run(self):
"""Check timeout on all responses. (Internal)"""
for req, resp in self.servings:
resp.check_timeout()
engine.timeout_monitor = _TimeoutMonitor(engine)
engine.timeout_monitor.subscribe()
engine.autoreload = process.plugins.Autoreloader(engine)
engine.autoreload.subscribe()
engine.thread_manager = process.plugins.ThreadManager(engine)
engine.thread_manager.subscribe()
engine.signal_handler = process.plugins.SignalHandler(engine)
from cherrypy import _cpserver
server = _cpserver.Server()
server.subscribe()
def quickstart(root=None, script_name="", config=None):
"""Mount the given root, start the builtin server (and engine), then block.
root: an instance of a "controller class" (a collection of page handler
methods) which represents the root of the application.
script_name: a string containing the "mount point" of the application.
This should start with a slash, and be the path portion of the URL
at which to mount the given root. For example, if root.index() will
handle requests to "http://www.example.com:8080/dept/app1/", then
the script_name argument would be "/dept/app1".
It MUST NOT end in a slash. If the script_name refers to the root
of the URI, it MUST be an empty string (not "/").
config: a file or dict containing application config. If this contains
a [global] section, those entries will be used in the global
(site-wide) config.
"""
if config:
_global_conf_alias.update(config)
tree.mount(root, script_name, config)
if hasattr(engine, "signal_handler"):
engine.signal_handler.subscribe()
if hasattr(engine, "console_control_handler"):
engine.console_control_handler.subscribe()
engine.start()
engine.block()
from cherrypy._cpcompat import threadlocal as _local
class _Serving(_local):
"""An interface for registering request and response objects.
Rather than have a separate "thread local" object for the request and
the response, this class works as a single threadlocal container for
both objects (and any others which developers wish to define). In this
way, we can easily dump those objects when we stop/start a new HTTP
conversation, yet still refer to them as module-level globals in a
thread-safe way.
"""
request = _cprequest.Request(_httputil.Host("127.0.0.1", 80),
_httputil.Host("127.0.0.1", 1111))
"""
The request object for the current thread. In the main thread,
and any threads which are not receiving HTTP requests, this is None."""
response = _cprequest.Response()
"""
The response object for the current thread. In the main thread,
and any threads which are not receiving HTTP requests, this is None."""
def load(self, request, response):
self.request = request
self.response = response
def clear(self):
"""Remove all attributes of self."""
self.__dict__.clear()
serving = _Serving()
class _ThreadLocalProxy(object):
__slots__ = ['__attrname__', '__dict__']
def __init__(self, attrname):
self.__attrname__ = attrname
def __getattr__(self, name):
child = getattr(serving, self.__attrname__)
return getattr(child, name)
def __setattr__(self, name, value):
if name in ("__attrname__", ):
object.__setattr__(self, name, value)
else:
child = getattr(serving, self.__attrname__)
setattr(child, name, value)
def __delattr__(self, name):
child = getattr(serving, self.__attrname__)
delattr(child, name)
def _get_dict(self):
child = getattr(serving, self.__attrname__)
d = child.__class__.__dict__.copy()
d.update(child.__dict__)
return d
__dict__ = property(_get_dict)
def __getitem__(self, key):
child = getattr(serving, self.__attrname__)
return child[key]
def __setitem__(self, key, value):
child = getattr(serving, self.__attrname__)
child[key] = value
def __delitem__(self, key):
child = getattr(serving, self.__attrname__)
del child[key]
def __contains__(self, key):
child = getattr(serving, self.__attrname__)
return key in child
def __len__(self):
child = getattr(serving, self.__attrname__)
return len(child)
def __nonzero__(self):
child = getattr(serving, self.__attrname__)
return bool(child)
# Python 3
__bool__ = __nonzero__
# Create request and response object (the same objects will be used
# throughout the entire life of the webserver, but will redirect
# to the "serving" object)
request = _ThreadLocalProxy('request')
response = _ThreadLocalProxy('response')
# Create thread_data object as a thread-specific all-purpose storage
class _ThreadData(_local):
"""A container for thread-specific data."""
thread_data = _ThreadData()
# Monkeypatch pydoc to allow help() to go through the threadlocal proxy.
# Jan 2007: no Googleable examples of anyone else replacing pydoc.resolve.
# The only other way would be to change what is returned from type(request)
# and that's not possible in pure Python (you'd have to fake ob_type).
def _cherrypy_pydoc_resolve(thing, forceload=0):
"""Given an object or a path to an object, get the object and its name."""
if isinstance(thing, _ThreadLocalProxy):
thing = getattr(serving, thing.__attrname__)
return _pydoc._builtin_resolve(thing, forceload)
try:
import pydoc as _pydoc
_pydoc._builtin_resolve = _pydoc.resolve
_pydoc.resolve = _cherrypy_pydoc_resolve
except ImportError:
pass
from cherrypy import _cplogging
class _GlobalLogManager(_cplogging.LogManager):
"""A site-wide LogManager; routes to app.log or global log as appropriate.
This :class:`LogManager<cherrypy._cplogging.LogManager>` implements
cherrypy.log() and cherrypy.log.access(). If either
function is called during a request, the message will be sent to the
logger for the current Application. If they are called outside of a
request, the message will be sent to the site-wide logger.
"""
def __call__(self, *args, **kwargs):
"""Log the given message to the app.log or global log as appropriate."""
# Do NOT use try/except here. See http://www.cherrypy.org/ticket/945
if hasattr(request, 'app') and hasattr(request.app, 'log'):
log = request.app.log
else:
log = self
return log.error(*args, **kwargs)
def access(self):
"""Log an access message to the app.log or global log as appropriate."""
try:
return request.app.log.access()
except AttributeError:
return _cplogging.LogManager.access(self)
log = _GlobalLogManager()
# Set a default screen handler on the global log.
log.screen = True
log.error_file = ''
# Using an access file makes CP about 10% slower. Leave off by default.
log.access_file = ''
def _buslog(msg, level):
log.error(msg, 'ENGINE', severity=level)
engine.subscribe('log', _buslog)
# Helper functions for CP apps #
def expose(func=None, alias=None):
"""Expose the function, optionally providing an alias or set of aliases."""
def expose_(func):
func.exposed = True
if alias is not None:
if isinstance(alias, basestring):
parents[alias.replace(".", "_")] = func
else:
for a in alias:
parents[a.replace(".", "_")] = func
return func
import sys, types
if isinstance(func, (types.FunctionType, types.MethodType)):
if alias is None:
# @expose
func.exposed = True
return func
else:
# func = expose(func, alias)
parents = sys._getframe(1).f_locals
return expose_(func)
elif func is None:
if alias is None:
# @expose()
parents = sys._getframe(1).f_locals
return expose_
else:
# @expose(alias="alias") or
# @expose(alias=["alias1", "alias2"])
parents = sys._getframe(1).f_locals
return expose_
else:
# @expose("alias") or
# @expose(["alias1", "alias2"])
parents = sys._getframe(1).f_locals
alias = func
return expose_
def popargs(*args, **kwargs):
"""A decorator for _cp_dispatch
(cherrypy.dispatch.Dispatcher.dispatch_method_name).
Optional keyword argument: handler=(Object or Function)
Provides a _cp_dispatch function that pops off path segments into
cherrypy.request.params under the names specified. The dispatch
is then forwarded on to the next vpath element.
Note that any existing (and exposed) member function of the class that
popargs is applied to will override that value of the argument. For
instance, if you have a method named "list" on the class decorated with
popargs, then accessing "/list" will call that function instead of popping
it off as the requested parameter. This restriction applies to all
_cp_dispatch functions. The only way around this restriction is to create
a "blank class" whose only function is to provide _cp_dispatch.
If there are path elements after the arguments, or more arguments
are requested than are available in the vpath, then the 'handler'
keyword argument specifies the next object to handle the parameterized
request. If handler is not specified or is None, then self is used.
If handler is a function rather than an instance, then that function
will be called with the args specified and the return value from that
function used as the next object INSTEAD of adding the parameters to
cherrypy.request.args.
This decorator may be used in one of two ways:
As a class decorator:
@cherrypy.popargs('year', 'month', 'day')
class Blog:
def index(self, year=None, month=None, day=None):
#Process the parameters here; any url like
#/, /2009, /2009/12, or /2009/12/31
#will fill in the appropriate parameters.
def create(self):
#This link will still be available at /create. Defined functions
#take precedence over arguments.
Or as a member of a class:
class Blog:
_cp_dispatch = cherrypy.popargs('year', 'month', 'day')
#...
The handler argument may be used to mix arguments with built in functions.
For instance, the following setup allows different activities at the
day, month, and year level:
class DayHandler:
def index(self, year, month, day):
#Do something with this day; probably list entries
def delete(self, year, month, day):
#Delete all entries for this day
@cherrypy.popargs('day', handler=DayHandler())
class MonthHandler:
def index(self, year, month):
#Do something with this month; probably list entries
def delete(self, year, month):
#Delete all entries for this month
@cherrypy.popargs('month', handler=MonthHandler())
class YearHandler:
def index(self, year):
#Do something with this year
#...
@cherrypy.popargs('year', handler=YearHandler())
class Root:
def index(self):
#...
"""
#Since keyword arg comes after *args, we have to process it ourselves
#for lower versions of python.
handler = None
handler_call = False
for k,v in kwargs.items():
if k == 'handler':
handler = v
else:
raise TypeError(
"cherrypy.popargs() got an unexpected keyword argument '{0}'" \
.format(k)
)
import inspect
if handler is not None \
and (hasattr(handler, '__call__') or inspect.isclass(handler)):
handler_call = True
def decorated(cls_or_self=None, vpath=None):
if inspect.isclass(cls_or_self):
#cherrypy.popargs is a class decorator
cls = cls_or_self
setattr(cls, dispatch.Dispatcher.dispatch_method_name, decorated)
return cls
#We're in the actual function
self = cls_or_self
parms = {}
for arg in args:
if not vpath:
break
parms[arg] = vpath.pop(0)
if handler is not None:
if handler_call:
return handler(**parms)
else:
request.params.update(parms)
return handler
request.params.update(parms)
#If we are the ultimate handler, then to prevent our _cp_dispatch
#from being called again, we will resolve remaining elements through
#getattr() directly.
if vpath:
return getattr(self, vpath.pop(0), None)
else:
return self
return decorated
def url(path="", qs="", script_name=None, base=None, relative=None):
"""Create an absolute URL for the given path.
If 'path' starts with a slash ('/'), this will return
(base + script_name + path + qs).
If it does not start with a slash, this returns
(base + script_name [+ request.path_info] + path + qs).
If script_name is None, cherrypy.request will be used
to find a script_name, if available.
If base is None, cherrypy.request.base will be used (if available).
Note that you can use cherrypy.tools.proxy to change this.
Finally, note that this function can be used to obtain an absolute URL
for the current request path (minus the querystring) by passing no args.
If you call url(qs=cherrypy.request.query_string), you should get the
original browser URL (assuming no internal redirections).
If relative is None or not provided, request.app.relative_urls will
be used (if available, else False). If False, the output will be an
absolute URL (including the scheme, host, vhost, and script_name).
If True, the output will instead be a URL that is relative to the
current request path, perhaps including '..' atoms. If relative is
the string 'server', the output will instead be a URL that is
relative to the server root; i.e., it will start with a slash.
"""
if isinstance(qs, (tuple, list, dict)):
qs = _urlencode(qs)
if qs:
qs = '?' + qs
if request.app:
if not path.startswith("/"):
# Append/remove trailing slash from path_info as needed
# (this is to support mistyped URL's without redirecting;
# if you want to redirect, use tools.trailing_slash).
pi = request.path_info
if request.is_index is True:
if not pi.endswith('/'):
pi = pi + '/'
elif request.is_index is False:
if pi.endswith('/') and pi != '/':
pi = pi[:-1]
if path == "":
path = pi
else:
path = _urljoin(pi, path)
if script_name is None:
script_name = request.script_name
if base is None:
base = request.base
newurl = base + script_name + path + qs
else:
# No request.app (we're being called outside a request).
# We'll have to guess the base from server.* attributes.
# This will produce very different results from the above
# if you're using vhosts or tools.proxy.
if base is None:
base = server.base()
path = (script_name or "") + path
newurl = base + path + qs
if './' in newurl:
# Normalize the URL by removing ./ and ../
atoms = []
for atom in newurl.split('/'):
if atom == '.':
pass
elif atom == '..':
atoms.pop()
else:
atoms.append(atom)
newurl = '/'.join(atoms)
# At this point, we should have a fully-qualified absolute URL.
if relative is None:
relative = getattr(request.app, "relative_urls", False)
# See http://www.ietf.org/rfc/rfc2396.txt
if relative == 'server':
# "A relative reference beginning with a single slash character is
# termed an absolute-path reference, as defined by <abs_path>..."
# This is also sometimes called "server-relative".
newurl = '/' + '/'.join(newurl.split('/', 3)[3:])
elif relative:
# "A relative reference that does not begin with a scheme name
# or a slash character is termed a relative-path reference."
old = url(relative=False).split('/')[:-1]
new = newurl.split('/')
while old and new:
a, b = old[0], new[0]
if a != b:
break
old.pop(0)
new.pop(0)
new = (['..'] * len(old)) + new
newurl = '/'.join(new)
return newurl
# import _cpconfig last so it can reference other top-level objects
from cherrypy import _cpconfig
# Use _global_conf_alias so quickstart can use 'config' as an arg
# without shadowing cherrypy.config.
config = _global_conf_alias = _cpconfig.Config()
config.defaults = {
'tools.log_tracebacks.on': True,
'tools.log_headers.on': True,
'tools.trailing_slash.on': True,
'tools.encode.on': True
}
config.namespaces["log"] = lambda k, v: setattr(log, k, v)
config.namespaces["checker"] = lambda k, v: setattr(checker, k, v)
# Must reset to get our defaults applied.
config.reset()
from cherrypy import _cpchecker
checker = _cpchecker.Checker()
engine.subscribe('start', checker)

View File

@ -1,327 +0,0 @@
import os
import warnings
import cherrypy
from cherrypy._cpcompat import iteritems, copykeys, builtins
class Checker(object):
"""A checker for CherryPy sites and their mounted applications.
When this object is called at engine startup, it executes each
of its own methods whose names start with ``check_``. If you wish
to disable selected checks, simply add a line in your global
config which sets the appropriate method to False::
[global]
checker.check_skipped_app_config = False
You may also dynamically add or replace ``check_*`` methods in this way.
"""
on = True
"""If True (the default), run all checks; if False, turn off all checks."""
def __init__(self):
self._populate_known_types()
def __call__(self):
"""Run all check_* methods."""
if self.on:
oldformatwarning = warnings.formatwarning
warnings.formatwarning = self.formatwarning
try:
for name in dir(self):
if name.startswith("check_"):
method = getattr(self, name)
if method and hasattr(method, '__call__'):
method()
finally:
warnings.formatwarning = oldformatwarning
def formatwarning(self, message, category, filename, lineno, line=None):
"""Function to format a warning."""
return "CherryPy Checker:\n%s\n\n" % message
# This value should be set inside _cpconfig.
global_config_contained_paths = False
def check_app_config_entries_dont_start_with_script_name(self):
"""Check for Application config with sections that repeat script_name."""
for sn, app in cherrypy.tree.apps.items():
if not isinstance(app, cherrypy.Application):
continue
if not app.config:
continue
if sn == '':
continue
sn_atoms = sn.strip("/").split("/")
for key in app.config.keys():
key_atoms = key.strip("/").split("/")
if key_atoms[:len(sn_atoms)] == sn_atoms:
warnings.warn(
"The application mounted at %r has config " \
"entries that start with its script name: %r" % (sn, key))
def check_site_config_entries_in_app_config(self):
"""Check for mounted Applications that have site-scoped config."""
for sn, app in iteritems(cherrypy.tree.apps):
if not isinstance(app, cherrypy.Application):
continue
msg = []
for section, entries in iteritems(app.config):
if section.startswith('/'):
for key, value in iteritems(entries):
for n in ("engine.", "server.", "tree.", "checker."):
if key.startswith(n):
msg.append("[%s] %s = %s" % (section, key, value))
if msg:
msg.insert(0,
"The application mounted at %r contains the following "
"config entries, which are only allowed in site-wide "
"config. Move them to a [global] section and pass them "
"to cherrypy.config.update() instead of tree.mount()." % sn)
warnings.warn(os.linesep.join(msg))
def check_skipped_app_config(self):
"""Check for mounted Applications that have no config."""
for sn, app in cherrypy.tree.apps.items():
if not isinstance(app, cherrypy.Application):
continue
if not app.config:
msg = "The Application mounted at %r has an empty config." % sn
if self.global_config_contained_paths:
msg += (" It looks like the config you passed to "
"cherrypy.config.update() contains application-"
"specific sections. You must explicitly pass "
"application config via "
"cherrypy.tree.mount(..., config=app_config)")
warnings.warn(msg)
return
def check_app_config_brackets(self):
"""Check for Application config with extraneous brackets in section names."""
for sn, app in cherrypy.tree.apps.items():
if not isinstance(app, cherrypy.Application):
continue
if not app.config:
continue
for key in app.config.keys():
if key.startswith("[") or key.endswith("]"):
warnings.warn(
"The application mounted at %r has config " \
"section names with extraneous brackets: %r. "
"Config *files* need brackets; config *dicts* "
"(e.g. passed to tree.mount) do not." % (sn, key))
def check_static_paths(self):
"""Check Application config for incorrect static paths."""
# Use the dummy Request object in the main thread.
request = cherrypy.request
for sn, app in cherrypy.tree.apps.items():
if not isinstance(app, cherrypy.Application):
continue
request.app = app
for section in app.config:
# get_resource will populate request.config
request.get_resource(section + "/dummy.html")
conf = request.config.get
if conf("tools.staticdir.on", False):
msg = ""
root = conf("tools.staticdir.root")
dir = conf("tools.staticdir.dir")
if dir is None:
msg = "tools.staticdir.dir is not set."
else:
fulldir = ""
if os.path.isabs(dir):
fulldir = dir
if root:
msg = ("dir is an absolute path, even "
"though a root is provided.")
testdir = os.path.join(root, dir[1:])
if os.path.exists(testdir):
msg += ("\nIf you meant to serve the "
"filesystem folder at %r, remove "
"the leading slash from dir." % testdir)
else:
if not root:
msg = "dir is a relative path and no root provided."
else:
fulldir = os.path.join(root, dir)
if not os.path.isabs(fulldir):
msg = "%r is not an absolute path." % fulldir
if fulldir and not os.path.exists(fulldir):
if msg:
msg += "\n"
msg += ("%r (root + dir) is not an existing "
"filesystem path." % fulldir)
if msg:
warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
% (msg, section, root, dir))
# -------------------------- Compatibility -------------------------- #
obsolete = {
'server.default_content_type': 'tools.response_headers.headers',
'log_access_file': 'log.access_file',
'log_config_options': None,
'log_file': 'log.error_file',
'log_file_not_found': None,
'log_request_headers': 'tools.log_headers.on',
'log_to_screen': 'log.screen',
'show_tracebacks': 'request.show_tracebacks',
'throw_errors': 'request.throw_errors',
'profiler.on': ('cherrypy.tree.mount(profiler.make_app('
'cherrypy.Application(Root())))'),
}
deprecated = {}
def _compat(self, config):
"""Process config and warn on each obsolete or deprecated entry."""
for section, conf in config.items():
if isinstance(conf, dict):
for k, v in conf.items():
if k in self.obsolete:
warnings.warn("%r is obsolete. Use %r instead.\n"
"section: [%s]" %
(k, self.obsolete[k], section))
elif k in self.deprecated:
warnings.warn("%r is deprecated. Use %r instead.\n"
"section: [%s]" %
(k, self.deprecated[k], section))
else:
if section in self.obsolete:
warnings.warn("%r is obsolete. Use %r instead."
% (section, self.obsolete[section]))
elif section in self.deprecated:
warnings.warn("%r is deprecated. Use %r instead."
% (section, self.deprecated[section]))
def check_compatibility(self):
"""Process config and warn on each obsolete or deprecated entry."""
self._compat(cherrypy.config)
for sn, app in cherrypy.tree.apps.items():
if not isinstance(app, cherrypy.Application):
continue
self._compat(app.config)
# ------------------------ Known Namespaces ------------------------ #
extra_config_namespaces = []
def _known_ns(self, app):
ns = ["wsgi"]
ns.extend(copykeys(app.toolboxes))
ns.extend(copykeys(app.namespaces))
ns.extend(copykeys(app.request_class.namespaces))
ns.extend(copykeys(cherrypy.config.namespaces))
ns += self.extra_config_namespaces
for section, conf in app.config.items():
is_path_section = section.startswith("/")
if is_path_section and isinstance(conf, dict):
for k, v in conf.items():
atoms = k.split(".")
if len(atoms) > 1:
if atoms[0] not in ns:
# Spit out a special warning if a known
# namespace is preceded by "cherrypy."
if (atoms[0] == "cherrypy" and atoms[1] in ns):
msg = ("The config entry %r is invalid; "
"try %r instead.\nsection: [%s]"
% (k, ".".join(atoms[1:]), section))
else:
msg = ("The config entry %r is invalid, because "
"the %r config namespace is unknown.\n"
"section: [%s]" % (k, atoms[0], section))
warnings.warn(msg)
elif atoms[0] == "tools":
if atoms[1] not in dir(cherrypy.tools):
msg = ("The config entry %r may be invalid, "
"because the %r tool was not found.\n"
"section: [%s]" % (k, atoms[1], section))
warnings.warn(msg)
def check_config_namespaces(self):
"""Process config and warn on each unknown config namespace."""
for sn, app in cherrypy.tree.apps.items():
if not isinstance(app, cherrypy.Application):
continue
self._known_ns(app)
# -------------------------- Config Types -------------------------- #
known_config_types = {}
def _populate_known_types(self):
b = [x for x in vars(builtins).values()
if type(x) is type(str)]
def traverse(obj, namespace):
for name in dir(obj):
# Hack for 3.2's warning about body_params
if name == 'body_params':
continue
vtype = type(getattr(obj, name, None))
if vtype in b:
self.known_config_types[namespace + "." + name] = vtype
traverse(cherrypy.request, "request")
traverse(cherrypy.response, "response")
traverse(cherrypy.server, "server")
traverse(cherrypy.engine, "engine")
traverse(cherrypy.log, "log")
def _known_types(self, config):
msg = ("The config entry %r in section %r is of type %r, "
"which does not match the expected type %r.")
for section, conf in config.items():
if isinstance(conf, dict):
for k, v in conf.items():
if v is not None:
expected_type = self.known_config_types.get(k, None)
vtype = type(v)
if expected_type and vtype != expected_type:
warnings.warn(msg % (k, section, vtype.__name__,
expected_type.__name__))
else:
k, v = section, conf
if v is not None:
expected_type = self.known_config_types.get(k, None)
vtype = type(v)
if expected_type and vtype != expected_type:
warnings.warn(msg % (k, section, vtype.__name__,
expected_type.__name__))
def check_config_types(self):
"""Assert that config values are of the same type as default values."""
self._known_types(cherrypy.config)
for sn, app in cherrypy.tree.apps.items():
if not isinstance(app, cherrypy.Application):
continue
self._known_types(app.config)
# -------------------- Specific config warnings -------------------- #
def check_localhost(self):
"""Warn if any socket_host is 'localhost'. See #711."""
for k, v in cherrypy.config.items():
if k == 'server.socket_host' and v == 'localhost':
warnings.warn("The use of 'localhost' as a socket host can "
"cause problems on newer systems, since 'localhost' can "
"map to either an IPv4 or an IPv6 address. You should "
"use '127.0.0.1' or '[::1]' instead.")

View File

@ -1,318 +0,0 @@
"""Compatibility code for using CherryPy with various versions of Python.
CherryPy 3.2 is compatible with Python versions 2.3+. This module provides a
useful abstraction over the differences between Python versions, sometimes by
preferring a newer idiom, sometimes an older one, and sometimes a custom one.
In particular, Python 2 uses str and '' for byte strings, while Python 3
uses str and '' for unicode strings. We will call each of these the 'native
string' type for each version. Because of this major difference, this module
provides new 'bytestr', 'unicodestr', and 'nativestr' attributes, as well as
two functions: 'ntob', which translates native strings (of type 'str') into
byte strings regardless of Python version, and 'ntou', which translates native
strings to unicode strings. This also provides a 'BytesIO' name for dealing
specifically with bytes, and a 'StringIO' name for dealing with native strings.
It also provides a 'base64_decode' function with native strings as input and
output.
"""
import os
import re
import sys
if sys.version_info >= (3, 0):
py3k = True
bytestr = bytes
unicodestr = str
nativestr = unicodestr
basestring = (bytes, str)
def ntob(n, encoding='ISO-8859-1'):
"""Return the given native string as a byte string in the given encoding."""
# In Python 3, the native string type is unicode
return n.encode(encoding)
def ntou(n, encoding='ISO-8859-1'):
"""Return the given native string as a unicode string with the given encoding."""
# In Python 3, the native string type is unicode
return n
def tonative(n, encoding='ISO-8859-1'):
"""Return the given string as a native string in the given encoding."""
# In Python 3, the native string type is unicode
if isinstance(n, bytes):
return n.decode(encoding)
return n
# type("")
from io import StringIO
# bytes:
from io import BytesIO as BytesIO
else:
# Python 2
py3k = False
bytestr = str
unicodestr = unicode
nativestr = bytestr
basestring = basestring
def ntob(n, encoding='ISO-8859-1'):
"""Return the given native string as a byte string in the given encoding."""
# In Python 2, the native string type is bytes. Assume it's already
# in the given encoding, which for ISO-8859-1 is almost always what
# was intended.
return n
def ntou(n, encoding='ISO-8859-1'):
"""Return the given native string as a unicode string with the given encoding."""
# In Python 2, the native string type is bytes.
# First, check for the special encoding 'escape'. The test suite uses this
# to signal that it wants to pass a string with embedded \uXXXX escapes,
# but without having to prefix it with u'' for Python 2, but no prefix
# for Python 3.
if encoding == 'escape':
return unicode(
re.sub(r'\\u([0-9a-zA-Z]{4})',
lambda m: unichr(int(m.group(1), 16)),
n.decode('ISO-8859-1')))
# Assume it's already in the given encoding, which for ISO-8859-1 is almost
# always what was intended.
return n.decode(encoding)
def tonative(n, encoding='ISO-8859-1'):
"""Return the given string as a native string in the given encoding."""
# In Python 2, the native string type is bytes.
if isinstance(n, unicode):
return n.encode(encoding)
return n
try:
# type("")
from cStringIO import StringIO
except ImportError:
# type("")
from StringIO import StringIO
# bytes:
BytesIO = StringIO
try:
set = set
except NameError:
from sets import Set as set
try:
# Python 3.1+
from base64 import decodebytes as _base64_decodebytes
except ImportError:
# Python 3.0-
# since CherryPy claims compatibility with Python 2.3, we must use
# the legacy API of base64
from base64 import decodestring as _base64_decodebytes
def base64_decode(n, encoding='ISO-8859-1'):
"""Return the native string base64-decoded (as a native string)."""
if isinstance(n, unicodestr):
b = n.encode(encoding)
else:
b = n
b = _base64_decodebytes(b)
if nativestr is unicodestr:
return b.decode(encoding)
else:
return b
try:
# Python 2.5+
from hashlib import md5
except ImportError:
from md5 import new as md5
try:
# Python 2.5+
from hashlib import sha1 as sha
except ImportError:
from sha import new as sha
try:
sorted = sorted
except NameError:
def sorted(i):
i = i[:]
i.sort()
return i
try:
reversed = reversed
except NameError:
def reversed(x):
i = len(x)
while i > 0:
i -= 1
yield x[i]
try:
# Python 3
from urllib.parse import urljoin, urlencode
from urllib.parse import quote, quote_plus
from urllib.request import unquote, urlopen
from urllib.request import parse_http_list, parse_keqv_list
except ImportError:
# Python 2
from urlparse import urljoin
from urllib import urlencode, urlopen
from urllib import quote, quote_plus
from urllib import unquote
from urllib2 import parse_http_list, parse_keqv_list
try:
from threading import local as threadlocal
except ImportError:
from cherrypy._cpthreadinglocal import local as threadlocal
try:
dict.iteritems
# Python 2
iteritems = lambda d: d.iteritems()
copyitems = lambda d: d.items()
except AttributeError:
# Python 3
iteritems = lambda d: d.items()
copyitems = lambda d: list(d.items())
try:
dict.iterkeys
# Python 2
iterkeys = lambda d: d.iterkeys()
copykeys = lambda d: d.keys()
except AttributeError:
# Python 3
iterkeys = lambda d: d.keys()
copykeys = lambda d: list(d.keys())
try:
dict.itervalues
# Python 2
itervalues = lambda d: d.itervalues()
copyvalues = lambda d: d.values()
except AttributeError:
# Python 3
itervalues = lambda d: d.values()
copyvalues = lambda d: list(d.values())
try:
# Python 3
import builtins
except ImportError:
# Python 2
import __builtin__ as builtins
try:
# Python 2. We have to do it in this order so Python 2 builds
# don't try to import the 'http' module from cherrypy.lib
from Cookie import SimpleCookie, CookieError
from httplib import BadStatusLine, HTTPConnection, HTTPSConnection, IncompleteRead, NotConnected
from BaseHTTPServer import BaseHTTPRequestHandler
except ImportError:
# Python 3
from http.cookies import SimpleCookie, CookieError
from http.client import BadStatusLine, HTTPConnection, HTTPSConnection, IncompleteRead, NotConnected
from http.server import BaseHTTPRequestHandler
try:
# Python 2. We have to do it in this order so Python 2 builds
# don't try to import the 'http' module from cherrypy.lib
from httplib import HTTPSConnection
except ImportError:
try:
# Python 3
from http.client import HTTPSConnection
except ImportError:
# Some platforms which don't have SSL don't expose HTTPSConnection
HTTPSConnection = None
try:
# Python 2
xrange = xrange
except NameError:
# Python 3
xrange = range
import threading
if hasattr(threading.Thread, "daemon"):
# Python 2.6+
def get_daemon(t):
return t.daemon
def set_daemon(t, val):
t.daemon = val
else:
def get_daemon(t):
return t.isDaemon()
def set_daemon(t, val):
t.setDaemon(val)
try:
from email.utils import formatdate
def HTTPDate(timeval=None):
return formatdate(timeval, usegmt=True)
except ImportError:
from rfc822 import formatdate as HTTPDate
try:
# Python 3
from urllib.parse import unquote as parse_unquote
def unquote_qs(atom, encoding, errors='strict'):
return parse_unquote(atom.replace('+', ' '), encoding=encoding, errors=errors)
except ImportError:
# Python 2
from urllib import unquote as parse_unquote
def unquote_qs(atom, encoding, errors='strict'):
return parse_unquote(atom.replace('+', ' ')).decode(encoding, errors)
try:
# Prefer simplejson, which is usually more advanced than the builtin module.
import simplejson as json
json_decode = json.JSONDecoder().decode
json_encode = json.JSONEncoder().iterencode
except ImportError:
if py3k:
# Python 3.0: json is part of the standard library,
# but outputs unicode. We need bytes.
import json
json_decode = json.JSONDecoder().decode
_json_encode = json.JSONEncoder().iterencode
def json_encode(value):
for chunk in _json_encode(value):
yield chunk.encode('utf8')
elif sys.version_info >= (2, 6):
# Python 2.6: json is part of the standard library
import json
json_decode = json.JSONDecoder().decode
json_encode = json.JSONEncoder().iterencode
else:
json = None
def json_decode(s):
raise ValueError('No JSON library is available')
def json_encode(s):
raise ValueError('No JSON library is available')
try:
import cPickle as pickle
except ImportError:
# In Python 2, pickle is a Python version.
# In Python 3, pickle is the sped-up C version.
import pickle
try:
os.urandom(20)
import binascii
def random20():
return binascii.hexlify(os.urandom(20)).decode('ascii')
except (AttributeError, NotImplementedError):
import random
# os.urandom not available until Python 2.4. Fall back to random.random.
def random20():
return sha('%s' % random.random()).hexdigest()
try:
from _thread import get_ident as get_thread_ident
except ImportError:
from thread import get_ident as get_thread_ident
try:
# Python 3
next = next
except NameError:
# Python 2
def next(i):
return i.next()

View File

@ -1,295 +0,0 @@
"""
Configuration system for CherryPy.
Configuration in CherryPy is implemented via dictionaries. Keys are strings
which name the mapped value, which may be of any type.
Architecture
------------
CherryPy Requests are part of an Application, which runs in a global context,
and configuration data may apply to any of those three scopes:
Global
Configuration entries which apply everywhere are stored in
cherrypy.config.
Application
Entries which apply to each mounted application are stored
on the Application object itself, as 'app.config'. This is a two-level
dict where each key is a path, or "relative URL" (for example, "/" or
"/path/to/my/page"), and each value is a config dict. Usually, this
data is provided in the call to tree.mount(root(), config=conf),
although you may also use app.merge(conf).
Request
Each Request object possesses a single 'Request.config' dict.
Early in the request process, this dict is populated by merging global
config entries, Application entries (whose path equals or is a parent
of Request.path_info), and any config acquired while looking up the
page handler (see next).
Declaration
-----------
Configuration data may be supplied as a Python dictionary, as a filename,
or as an open file object. When you supply a filename or file, CherryPy
uses Python's builtin ConfigParser; you declare Application config by
writing each path as a section header::
[/path/to/my/page]
request.stream = True
To declare global configuration entries, place them in a [global] section.
You may also declare config entries directly on the classes and methods
(page handlers) that make up your CherryPy application via the ``_cp_config``
attribute. For example::
class Demo:
_cp_config = {'tools.gzip.on': True}
def index(self):
return "Hello world"
index.exposed = True
index._cp_config = {'request.show_tracebacks': False}
.. note::
This behavior is only guaranteed for the default dispatcher.
Other dispatchers may have different restrictions on where
you can attach _cp_config attributes.
Namespaces
----------
Configuration keys are separated into namespaces by the first "." in the key.
Current namespaces:
engine
Controls the 'application engine', including autoreload.
These can only be declared in the global config.
tree
Grafts cherrypy.Application objects onto cherrypy.tree.
These can only be declared in the global config.
hooks
Declares additional request-processing functions.
log
Configures the logging for each application.
These can only be declared in the global or / config.
request
Adds attributes to each Request.
response
Adds attributes to each Response.
server
Controls the default HTTP server via cherrypy.server.
These can only be declared in the global config.
tools
Runs and configures additional request-processing packages.
wsgi
Adds WSGI middleware to an Application's "pipeline".
These can only be declared in the app's root config ("/").
checker
Controls the 'checker', which looks for common errors in
app state (including config) when the engine starts.
Global config only.
The only key that does not exist in a namespace is the "environment" entry.
This special entry 'imports' other config entries from a template stored in
cherrypy._cpconfig.environments[environment]. It only applies to the global
config, and only when you use cherrypy.config.update.
You can define your own namespaces to be called at the Global, Application,
or Request level, by adding a named handler to cherrypy.config.namespaces,
app.namespaces, or app.request_class.namespaces. The name can
be any string, and the handler must be either a callable or a (Python 2.5
style) context manager.
"""
import cherrypy
from cherrypy._cpcompat import set, basestring
from cherrypy.lib import reprconf
# Deprecated in CherryPy 3.2--remove in 3.3
NamespaceSet = reprconf.NamespaceSet
def merge(base, other):
"""Merge one app config (from a dict, file, or filename) into another.
If the given config is a filename, it will be appended to
the list of files to monitor for "autoreload" changes.
"""
if isinstance(other, basestring):
cherrypy.engine.autoreload.files.add(other)
# Load other into base
for section, value_map in reprconf.as_dict(other).items():
if not isinstance(value_map, dict):
raise ValueError(
"Application config must include section headers, but the "
"config you tried to merge doesn't have any sections. "
"Wrap your config in another dict with paths as section "
"headers, for example: {'/': config}.")
base.setdefault(section, {}).update(value_map)
class Config(reprconf.Config):
"""The 'global' configuration data for the entire CherryPy process."""
def update(self, config):
"""Update self from a dict, file or filename."""
if isinstance(config, basestring):
# Filename
cherrypy.engine.autoreload.files.add(config)
reprconf.Config.update(self, config)
def _apply(self, config):
"""Update self from a dict."""
if isinstance(config.get("global", None), dict):
if len(config) > 1:
cherrypy.checker.global_config_contained_paths = True
config = config["global"]
if 'tools.staticdir.dir' in config:
config['tools.staticdir.section'] = "global"
reprconf.Config._apply(self, config)
def __call__(self, *args, **kwargs):
"""Decorator for page handlers to set _cp_config."""
if args:
raise TypeError(
"The cherrypy.config decorator does not accept positional "
"arguments; you must use keyword arguments.")
def tool_decorator(f):
if not hasattr(f, "_cp_config"):
f._cp_config = {}
for k, v in kwargs.items():
f._cp_config[k] = v
return f
return tool_decorator
Config.environments = environments = {
"staging": {
'engine.autoreload_on': False,
'checker.on': False,
'tools.log_headers.on': False,
'request.show_tracebacks': False,
'request.show_mismatched_params': False,
},
"production": {
'engine.autoreload_on': False,
'checker.on': False,
'tools.log_headers.on': False,
'request.show_tracebacks': False,
'request.show_mismatched_params': False,
'log.screen': False,
},
"embedded": {
# For use with CherryPy embedded in another deployment stack.
'engine.autoreload_on': False,
'checker.on': False,
'tools.log_headers.on': False,
'request.show_tracebacks': False,
'request.show_mismatched_params': False,
'log.screen': False,
'engine.SIGHUP': None,
'engine.SIGTERM': None,
},
"test_suite": {
'engine.autoreload_on': False,
'checker.on': False,
'tools.log_headers.on': False,
'request.show_tracebacks': True,
'request.show_mismatched_params': True,
'log.screen': False,
},
}
def _server_namespace_handler(k, v):
"""Config handler for the "server" namespace."""
atoms = k.split(".", 1)
if len(atoms) > 1:
# Special-case config keys of the form 'server.servername.socket_port'
# to configure additional HTTP servers.
if not hasattr(cherrypy, "servers"):
cherrypy.servers = {}
servername, k = atoms
if servername not in cherrypy.servers:
from cherrypy import _cpserver
cherrypy.servers[servername] = _cpserver.Server()
# On by default, but 'on = False' can unsubscribe it (see below).
cherrypy.servers[servername].subscribe()
if k == 'on':
if v:
cherrypy.servers[servername].subscribe()
else:
cherrypy.servers[servername].unsubscribe()
else:
setattr(cherrypy.servers[servername], k, v)
else:
setattr(cherrypy.server, k, v)
Config.namespaces["server"] = _server_namespace_handler
def _engine_namespace_handler(k, v):
"""Backward compatibility handler for the "engine" namespace."""
engine = cherrypy.engine
if k == 'autoreload_on':
if v:
engine.autoreload.subscribe()
else:
engine.autoreload.unsubscribe()
elif k == 'autoreload_frequency':
engine.autoreload.frequency = v
elif k == 'autoreload_match':
engine.autoreload.match = v
elif k == 'reload_files':
engine.autoreload.files = set(v)
elif k == 'deadlock_poll_freq':
engine.timeout_monitor.frequency = v
elif k == 'SIGHUP':
engine.listeners['SIGHUP'] = set([v])
elif k == 'SIGTERM':
engine.listeners['SIGTERM'] = set([v])
elif "." in k:
plugin, attrname = k.split(".", 1)
plugin = getattr(engine, plugin)
if attrname == 'on':
if v and hasattr(getattr(plugin, 'subscribe', None), '__call__'):
plugin.subscribe()
return
elif (not v) and hasattr(getattr(plugin, 'unsubscribe', None), '__call__'):
plugin.unsubscribe()
return
setattr(plugin, attrname, v)
else:
setattr(engine, k, v)
Config.namespaces["engine"] = _engine_namespace_handler
def _tree_namespace_handler(k, v):
"""Namespace handler for the 'tree' config namespace."""
if isinstance(v, dict):
for script_name, app in v.items():
cherrypy.tree.graft(app, script_name)
cherrypy.engine.log("Mounted: %s on %s" % (app, script_name or "/"))
else:
cherrypy.tree.graft(v, v.script_name)
cherrypy.engine.log("Mounted: %s on %s" % (v, v.script_name or "/"))
Config.namespaces["tree"] = _tree_namespace_handler

View File

@ -1,636 +0,0 @@
"""CherryPy dispatchers.
A 'dispatcher' is the object which looks up the 'page handler' callable
and collects config for the current request based on the path_info, other
request attributes, and the application architecture. The core calls the
dispatcher as early as possible, passing it a 'path_info' argument.
The default dispatcher discovers the page handler by matching path_info
to a hierarchical arrangement of objects, starting at request.app.root.
"""
import string
import sys
import types
try:
classtype = (type, types.ClassType)
except AttributeError:
classtype = type
import cherrypy
from cherrypy._cpcompat import set
class PageHandler(object):
"""Callable which sets response.body."""
def __init__(self, callable, *args, **kwargs):
self.callable = callable
self.args = args
self.kwargs = kwargs
def __call__(self):
try:
return self.callable(*self.args, **self.kwargs)
except TypeError:
x = sys.exc_info()[1]
try:
test_callable_spec(self.callable, self.args, self.kwargs)
except cherrypy.HTTPError:
raise sys.exc_info()[1]
except:
raise x
raise
def test_callable_spec(callable, callable_args, callable_kwargs):
"""
Inspect callable and test to see if the given args are suitable for it.
When an error occurs during the handler's invoking stage there are 2
erroneous cases:
1. Too many parameters passed to a function which doesn't define
one of *args or **kwargs.
2. Too little parameters are passed to the function.
There are 3 sources of parameters to a cherrypy handler.
1. query string parameters are passed as keyword parameters to the handler.
2. body parameters are also passed as keyword parameters.
3. when partial matching occurs, the final path atoms are passed as
positional args.
Both the query string and path atoms are part of the URI. If they are
incorrect, then a 404 Not Found should be raised. Conversely the body
parameters are part of the request; if they are invalid a 400 Bad Request.
"""
show_mismatched_params = getattr(
cherrypy.serving.request, 'show_mismatched_params', False)
try:
(args, varargs, varkw, defaults) = inspect.getargspec(callable)
except TypeError:
if isinstance(callable, object) and hasattr(callable, '__call__'):
(args, varargs, varkw, defaults) = inspect.getargspec(callable.__call__)
else:
# If it wasn't one of our own types, re-raise
# the original error
raise
if args and args[0] == 'self':
args = args[1:]
arg_usage = dict([(arg, 0,) for arg in args])
vararg_usage = 0
varkw_usage = 0
extra_kwargs = set()
for i, value in enumerate(callable_args):
try:
arg_usage[args[i]] += 1
except IndexError:
vararg_usage += 1
for key in callable_kwargs.keys():
try:
arg_usage[key] += 1
except KeyError:
varkw_usage += 1
extra_kwargs.add(key)
# figure out which args have defaults.
args_with_defaults = args[-len(defaults or []):]
for i, val in enumerate(defaults or []):
# Defaults take effect only when the arg hasn't been used yet.
if arg_usage[args_with_defaults[i]] == 0:
arg_usage[args_with_defaults[i]] += 1
missing_args = []
multiple_args = []
for key, usage in arg_usage.items():
if usage == 0:
missing_args.append(key)
elif usage > 1:
multiple_args.append(key)
if missing_args:
# In the case where the method allows body arguments
# there are 3 potential errors:
# 1. not enough query string parameters -> 404
# 2. not enough body parameters -> 400
# 3. not enough path parts (partial matches) -> 404
#
# We can't actually tell which case it is,
# so I'm raising a 404 because that covers 2/3 of the
# possibilities
#
# In the case where the method does not allow body
# arguments it's definitely a 404.
message = None
if show_mismatched_params:
message="Missing parameters: %s" % ",".join(missing_args)
raise cherrypy.HTTPError(404, message=message)
# the extra positional arguments come from the path - 404 Not Found
if not varargs and vararg_usage > 0:
raise cherrypy.HTTPError(404)
body_params = cherrypy.serving.request.body.params or {}
body_params = set(body_params.keys())
qs_params = set(callable_kwargs.keys()) - body_params
if multiple_args:
if qs_params.intersection(set(multiple_args)):
# If any of the multiple parameters came from the query string then
# it's a 404 Not Found
error = 404
else:
# Otherwise it's a 400 Bad Request
error = 400
message = None
if show_mismatched_params:
message="Multiple values for parameters: "\
"%s" % ",".join(multiple_args)
raise cherrypy.HTTPError(error, message=message)
if not varkw and varkw_usage > 0:
# If there were extra query string parameters, it's a 404 Not Found
extra_qs_params = set(qs_params).intersection(extra_kwargs)
if extra_qs_params:
message = None
if show_mismatched_params:
message="Unexpected query string "\
"parameters: %s" % ", ".join(extra_qs_params)
raise cherrypy.HTTPError(404, message=message)
# If there were any extra body parameters, it's a 400 Not Found
extra_body_params = set(body_params).intersection(extra_kwargs)
if extra_body_params:
message = None
if show_mismatched_params:
message="Unexpected body parameters: "\
"%s" % ", ".join(extra_body_params)
raise cherrypy.HTTPError(400, message=message)
try:
import inspect
except ImportError:
test_callable_spec = lambda callable, args, kwargs: None
class LateParamPageHandler(PageHandler):
"""When passing cherrypy.request.params to the page handler, we do not
want to capture that dict too early; we want to give tools like the
decoding tool a chance to modify the params dict in-between the lookup
of the handler and the actual calling of the handler. This subclass
takes that into account, and allows request.params to be 'bound late'
(it's more complicated than that, but that's the effect).
"""
def _get_kwargs(self):
kwargs = cherrypy.serving.request.params.copy()
if self._kwargs:
kwargs.update(self._kwargs)
return kwargs
def _set_kwargs(self, kwargs):
self._kwargs = kwargs
kwargs = property(_get_kwargs, _set_kwargs,
doc='page handler kwargs (with '
'cherrypy.request.params copied in)')
if sys.version_info < (3, 0):
punctuation_to_underscores = string.maketrans(
string.punctuation, '_' * len(string.punctuation))
def validate_translator(t):
if not isinstance(t, str) or len(t) != 256:
raise ValueError("The translate argument must be a str of len 256.")
else:
punctuation_to_underscores = str.maketrans(
string.punctuation, '_' * len(string.punctuation))
def validate_translator(t):
if not isinstance(t, dict):
raise ValueError("The translate argument must be a dict.")
class Dispatcher(object):
"""CherryPy Dispatcher which walks a tree of objects to find a handler.
The tree is rooted at cherrypy.request.app.root, and each hierarchical
component in the path_info argument is matched to a corresponding nested
attribute of the root object. Matching handlers must have an 'exposed'
attribute which evaluates to True. The special method name "index"
matches a URI which ends in a slash ("/"). The special method name
"default" may match a portion of the path_info (but only when no longer
substring of the path_info matches some other object).
This is the default, built-in dispatcher for CherryPy.
"""
dispatch_method_name = '_cp_dispatch'
"""
The name of the dispatch method that nodes may optionally implement
to provide their own dynamic dispatch algorithm.
"""
def __init__(self, dispatch_method_name=None,
translate=punctuation_to_underscores):
validate_translator(translate)
self.translate = translate
if dispatch_method_name:
self.dispatch_method_name = dispatch_method_name
def __call__(self, path_info):
"""Set handler and config for the current request."""
request = cherrypy.serving.request
func, vpath = self.find_handler(path_info)
if func:
# Decode any leftover %2F in the virtual_path atoms.
vpath = [x.replace("%2F", "/") for x in vpath]
request.handler = LateParamPageHandler(func, *vpath)
else:
request.handler = cherrypy.NotFound()
def find_handler(self, path):
"""Return the appropriate page handler, plus any virtual path.
This will return two objects. The first will be a callable,
which can be used to generate page output. Any parameters from
the query string or request body will be sent to that callable
as keyword arguments.
The callable is found by traversing the application's tree,
starting from cherrypy.request.app.root, and matching path
components to successive objects in the tree. For example, the
URL "/path/to/handler" might return root.path.to.handler.
The second object returned will be a list of names which are
'virtual path' components: parts of the URL which are dynamic,
and were not used when looking up the handler.
These virtual path components are passed to the handler as
positional arguments.
"""
request = cherrypy.serving.request
app = request.app
root = app.root
dispatch_name = self.dispatch_method_name
# Get config for the root object/path.
fullpath = [x for x in path.strip('/').split('/') if x] + ['index']
fullpath_len = len(fullpath)
segleft = fullpath_len
nodeconf = {}
if hasattr(root, "_cp_config"):
nodeconf.update(root._cp_config)
if "/" in app.config:
nodeconf.update(app.config["/"])
object_trail = [['root', root, nodeconf, segleft]]
node = root
iternames = fullpath[:]
while iternames:
name = iternames[0]
# map to legal Python identifiers (e.g. replace '.' with '_')
objname = name.translate(self.translate)
nodeconf = {}
subnode = getattr(node, objname, None)
pre_len = len(iternames)
if subnode is None:
dispatch = getattr(node, dispatch_name, None)
if dispatch and hasattr(dispatch, '__call__') and not \
getattr(dispatch, 'exposed', False) and \
pre_len > 1:
#Don't expose the hidden 'index' token to _cp_dispatch
#We skip this if pre_len == 1 since it makes no sense
#to call a dispatcher when we have no tokens left.
index_name = iternames.pop()
subnode = dispatch(vpath=iternames)
iternames.append(index_name)
else:
#We didn't find a path, but keep processing in case there
#is a default() handler.
iternames.pop(0)
else:
#We found the path, remove the vpath entry
iternames.pop(0)
segleft = len(iternames)
if segleft > pre_len:
#No path segment was removed. Raise an error.
raise cherrypy.CherryPyException(
"A vpath segment was added. Custom dispatchers may only "
+ "remove elements. While trying to process "
+ "{0} in {1}".format(name, fullpath)
)
elif segleft == pre_len:
#Assume that the handler used the current path segment, but
#did not pop it. This allows things like
#return getattr(self, vpath[0], None)
iternames.pop(0)
segleft -= 1
node = subnode
if node is not None:
# Get _cp_config attached to this node.
if hasattr(node, "_cp_config"):
nodeconf.update(node._cp_config)
# Mix in values from app.config for this path.
existing_len = fullpath_len - pre_len
if existing_len != 0:
curpath = '/' + '/'.join(fullpath[0:existing_len])
else:
curpath = ''
new_segs = fullpath[fullpath_len - pre_len:fullpath_len - segleft]
for seg in new_segs:
curpath += '/' + seg
if curpath in app.config:
nodeconf.update(app.config[curpath])
object_trail.append([name, node, nodeconf, segleft])
def set_conf():
"""Collapse all object_trail config into cherrypy.request.config."""
base = cherrypy.config.copy()
# Note that we merge the config from each node
# even if that node was None.
for name, obj, conf, segleft in object_trail:
base.update(conf)
if 'tools.staticdir.dir' in conf:
base['tools.staticdir.section'] = '/' + '/'.join(fullpath[0:fullpath_len - segleft])
return base
# Try successive objects (reverse order)
num_candidates = len(object_trail) - 1
for i in range(num_candidates, -1, -1):
name, candidate, nodeconf, segleft = object_trail[i]
if candidate is None:
continue
# Try a "default" method on the current leaf.
if hasattr(candidate, "default"):
defhandler = candidate.default
if getattr(defhandler, 'exposed', False):
# Insert any extra _cp_config from the default handler.
conf = getattr(defhandler, "_cp_config", {})
object_trail.insert(i+1, ["default", defhandler, conf, segleft])
request.config = set_conf()
# See http://www.cherrypy.org/ticket/613
request.is_index = path.endswith("/")
return defhandler, fullpath[fullpath_len - segleft:-1]
# Uncomment the next line to restrict positional params to "default".
# if i < num_candidates - 2: continue
# Try the current leaf.
if getattr(candidate, 'exposed', False):
request.config = set_conf()
if i == num_candidates:
# We found the extra ".index". Mark request so tools
# can redirect if path_info has no trailing slash.
request.is_index = True
else:
# We're not at an 'index' handler. Mark request so tools
# can redirect if path_info has NO trailing slash.
# Note that this also includes handlers which take
# positional parameters (virtual paths).
request.is_index = False
return candidate, fullpath[fullpath_len - segleft:-1]
# We didn't find anything
request.config = set_conf()
return None, []
class MethodDispatcher(Dispatcher):
"""Additional dispatch based on cherrypy.request.method.upper().
Methods named GET, POST, etc will be called on an exposed class.
The method names must be all caps; the appropriate Allow header
will be output showing all capitalized method names as allowable
HTTP verbs.
Note that the containing class must be exposed, not the methods.
"""
def __call__(self, path_info):
"""Set handler and config for the current request."""
request = cherrypy.serving.request
resource, vpath = self.find_handler(path_info)
if resource:
# Set Allow header
avail = [m for m in dir(resource) if m.isupper()]
if "GET" in avail and "HEAD" not in avail:
avail.append("HEAD")
avail.sort()
cherrypy.serving.response.headers['Allow'] = ", ".join(avail)
# Find the subhandler
meth = request.method.upper()
func = getattr(resource, meth, None)
if func is None and meth == "HEAD":
func = getattr(resource, "GET", None)
if func:
# Grab any _cp_config on the subhandler.
if hasattr(func, "_cp_config"):
request.config.update(func._cp_config)
# Decode any leftover %2F in the virtual_path atoms.
vpath = [x.replace("%2F", "/") for x in vpath]
request.handler = LateParamPageHandler(func, *vpath)
else:
request.handler = cherrypy.HTTPError(405)
else:
request.handler = cherrypy.NotFound()
class RoutesDispatcher(object):
"""A Routes based dispatcher for CherryPy."""
def __init__(self, full_result=False):
"""
Routes dispatcher
Set full_result to True if you wish the controller
and the action to be passed on to the page handler
parameters. By default they won't be.
"""
import routes
self.full_result = full_result
self.controllers = {}
self.mapper = routes.Mapper()
self.mapper.controller_scan = self.controllers.keys
def connect(self, name, route, controller, **kwargs):
self.controllers[name] = controller
self.mapper.connect(name, route, controller=name, **kwargs)
def redirect(self, url):
raise cherrypy.HTTPRedirect(url)
def __call__(self, path_info):
"""Set handler and config for the current request."""
func = self.find_handler(path_info)
if func:
cherrypy.serving.request.handler = LateParamPageHandler(func)
else:
cherrypy.serving.request.handler = cherrypy.NotFound()
def find_handler(self, path_info):
"""Find the right page handler, and set request.config."""
import routes
request = cherrypy.serving.request
config = routes.request_config()
config.mapper = self.mapper
if hasattr(request, 'wsgi_environ'):
config.environ = request.wsgi_environ
config.host = request.headers.get('Host', None)
config.protocol = request.scheme
config.redirect = self.redirect
result = self.mapper.match(path_info)
config.mapper_dict = result
params = {}
if result:
params = result.copy()
if not self.full_result:
params.pop('controller', None)
params.pop('action', None)
request.params.update(params)
# Get config for the root object/path.
request.config = base = cherrypy.config.copy()
curpath = ""
def merge(nodeconf):
if 'tools.staticdir.dir' in nodeconf:
nodeconf['tools.staticdir.section'] = curpath or "/"
base.update(nodeconf)
app = request.app
root = app.root
if hasattr(root, "_cp_config"):
merge(root._cp_config)
if "/" in app.config:
merge(app.config["/"])
# Mix in values from app.config.
atoms = [x for x in path_info.split("/") if x]
if atoms:
last = atoms.pop()
else:
last = None
for atom in atoms:
curpath = "/".join((curpath, atom))
if curpath in app.config:
merge(app.config[curpath])
handler = None
if result:
controller = result.get('controller')
controller = self.controllers.get(controller, controller)
if controller:
if isinstance(controller, classtype):
controller = controller()
# Get config from the controller.
if hasattr(controller, "_cp_config"):
merge(controller._cp_config)
action = result.get('action')
if action is not None:
handler = getattr(controller, action, None)
# Get config from the handler
if hasattr(handler, "_cp_config"):
merge(handler._cp_config)
else:
handler = controller
# Do the last path atom here so it can
# override the controller's _cp_config.
if last:
curpath = "/".join((curpath, last))
if curpath in app.config:
merge(app.config[curpath])
return handler
def XMLRPCDispatcher(next_dispatcher=Dispatcher()):
from cherrypy.lib import xmlrpcutil
def xmlrpc_dispatch(path_info):
path_info = xmlrpcutil.patched_path(path_info)
return next_dispatcher(path_info)
return xmlrpc_dispatch
def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True, **domains):
"""
Select a different handler based on the Host header.
This can be useful when running multiple sites within one CP server.
It allows several domains to point to different parts of a single
website structure. For example::
http://www.domain.example -> root
http://www.domain2.example -> root/domain2/
http://www.domain2.example:443 -> root/secure
can be accomplished via the following config::
[/]
request.dispatch = cherrypy.dispatch.VirtualHost(
**{'www.domain2.example': '/domain2',
'www.domain2.example:443': '/secure',
})
next_dispatcher
The next dispatcher object in the dispatch chain.
The VirtualHost dispatcher adds a prefix to the URL and calls
another dispatcher. Defaults to cherrypy.dispatch.Dispatcher().
use_x_forwarded_host
If True (the default), any "X-Forwarded-Host"
request header will be used instead of the "Host" header. This
is commonly added by HTTP servers (such as Apache) when proxying.
``**domains``
A dict of {host header value: virtual prefix} pairs.
The incoming "Host" request header is looked up in this dict,
and, if a match is found, the corresponding "virtual prefix"
value will be prepended to the URL path before calling the
next dispatcher. Note that you often need separate entries
for "example.com" and "www.example.com". In addition, "Host"
headers may contain the port number.
"""
from cherrypy.lib import httputil
def vhost_dispatch(path_info):
request = cherrypy.serving.request
header = request.headers.get
domain = header('Host', '')
if use_x_forwarded_host:
domain = header("X-Forwarded-Host", domain)
prefix = domains.get(domain, "")
if prefix:
path_info = httputil.urljoin(prefix, path_info)
result = next_dispatcher(path_info)
# Touch up staticdir config. See http://www.cherrypy.org/ticket/614.
section = request.config.get('tools.staticdir.section')
if section:
section = section[len(prefix):]
request.config['tools.staticdir.section'] = section
return result
return vhost_dispatch

View File

@ -1,556 +0,0 @@
"""Exception classes for CherryPy.
CherryPy provides (and uses) exceptions for declaring that the HTTP response
should be a status other than the default "200 OK". You can ``raise`` them like
normal Python exceptions. You can also call them and they will raise themselves;
this means you can set an :class:`HTTPError<cherrypy._cperror.HTTPError>`
or :class:`HTTPRedirect<cherrypy._cperror.HTTPRedirect>` as the
:attr:`request.handler<cherrypy._cprequest.Request.handler>`.
.. _redirectingpost:
Redirecting POST
================
When you GET a resource and are redirected by the server to another Location,
there's generally no problem since GET is both a "safe method" (there should
be no side-effects) and an "idempotent method" (multiple calls are no different
than a single call).
POST, however, is neither safe nor idempotent--if you
charge a credit card, you don't want to be charged twice by a redirect!
For this reason, *none* of the 3xx responses permit a user-agent (browser) to
resubmit a POST on redirection without first confirming the action with the user:
===== ================================= ===========
300 Multiple Choices Confirm with the user
301 Moved Permanently Confirm with the user
302 Found (Object moved temporarily) Confirm with the user
303 See Other GET the new URI--no confirmation
304 Not modified (for conditional GET only--POST should not raise this error)
305 Use Proxy Confirm with the user
307 Temporary Redirect Confirm with the user
===== ================================= ===========
However, browsers have historically implemented these restrictions poorly;
in particular, many browsers do not force the user to confirm 301, 302
or 307 when redirecting POST. For this reason, CherryPy defaults to 303,
which most user-agents appear to have implemented correctly. Therefore, if
you raise HTTPRedirect for a POST request, the user-agent will most likely
attempt to GET the new URI (without asking for confirmation from the user).
We realize this is confusing for developers, but it's the safest thing we
could do. You are of course free to raise ``HTTPRedirect(uri, status=302)``
or any other 3xx status if you know what you're doing, but given the
environment, we couldn't let any of those be the default.
Custom Error Handling
=====================
.. image:: /refman/cperrors.gif
Anticipated HTTP responses
--------------------------
The 'error_page' config namespace can be used to provide custom HTML output for
expected responses (like 404 Not Found). Supply a filename from which the output
will be read. The contents will be interpolated with the values %(status)s,
%(message)s, %(traceback)s, and %(version)s using plain old Python
`string formatting <http://www.python.org/doc/2.6.4/library/stdtypes.html#string-formatting-operations>`_.
::
_cp_config = {'error_page.404': os.path.join(localDir, "static/index.html")}
Beginning in version 3.1, you may also provide a function or other callable as
an error_page entry. It will be passed the same status, message, traceback and
version arguments that are interpolated into templates::
def error_page_402(status, message, traceback, version):
return "Error %s - Well, I'm very sorry but you haven't paid!" % status
cherrypy.config.update({'error_page.402': error_page_402})
Also in 3.1, in addition to the numbered error codes, you may also supply
"error_page.default" to handle all codes which do not have their own error_page entry.
Unanticipated errors
--------------------
CherryPy also has a generic error handling mechanism: whenever an unanticipated
error occurs in your code, it will call
:func:`Request.error_response<cherrypy._cprequest.Request.error_response>` to set
the response status, headers, and body. By default, this is the same output as
:class:`HTTPError(500) <cherrypy._cperror.HTTPError>`. If you want to provide
some other behavior, you generally replace "request.error_response".
Here is some sample code that shows how to display a custom error message and
send an e-mail containing the error::
from cherrypy import _cperror
def handle_error():
cherrypy.response.status = 500
cherrypy.response.body = ["<html><body>Sorry, an error occured</body></html>"]
sendMail('error@domain.com', 'Error in your web app', _cperror.format_exc())
class Root:
_cp_config = {'request.error_response': handle_error}
Note that you have to explicitly set :attr:`response.body <cherrypy._cprequest.Response.body>`
and not simply return an error message as a result.
"""
from cgi import escape as _escape
from sys import exc_info as _exc_info
from traceback import format_exception as _format_exception
from cherrypy._cpcompat import basestring, bytestr, iteritems, ntob, tonative, urljoin as _urljoin
from cherrypy.lib import httputil as _httputil
class CherryPyException(Exception):
"""A base class for CherryPy exceptions."""
pass
class TimeoutError(CherryPyException):
"""Exception raised when Response.timed_out is detected."""
pass
class InternalRedirect(CherryPyException):
"""Exception raised to switch to the handler for a different URL.
This exception will redirect processing to another path within the site
(without informing the client). Provide the new path as an argument when
raising the exception. Provide any params in the querystring for the new URL.
"""
def __init__(self, path, query_string=""):
import cherrypy
self.request = cherrypy.serving.request
self.query_string = query_string
if "?" in path:
# Separate any params included in the path
path, self.query_string = path.split("?", 1)
# Note that urljoin will "do the right thing" whether url is:
# 1. a URL relative to root (e.g. "/dummy")
# 2. a URL relative to the current path
# Note that any query string will be discarded.
path = _urljoin(self.request.path_info, path)
# Set a 'path' member attribute so that code which traps this
# error can have access to it.
self.path = path
CherryPyException.__init__(self, path, self.query_string)
class HTTPRedirect(CherryPyException):
"""Exception raised when the request should be redirected.
This exception will force a HTTP redirect to the URL or URL's you give it.
The new URL must be passed as the first argument to the Exception,
e.g., HTTPRedirect(newUrl). Multiple URLs are allowed in a list.
If a URL is absolute, it will be used as-is. If it is relative, it is
assumed to be relative to the current cherrypy.request.path_info.
If one of the provided URL is a unicode object, it will be encoded
using the default encoding or the one passed in parameter.
There are multiple types of redirect, from which you can select via the
``status`` argument. If you do not provide a ``status`` arg, it defaults to
303 (or 302 if responding with HTTP/1.0).
Examples::
raise cherrypy.HTTPRedirect("")
raise cherrypy.HTTPRedirect("/abs/path", 307)
raise cherrypy.HTTPRedirect(["path1", "path2?a=1&b=2"], 301)
See :ref:`redirectingpost` for additional caveats.
"""
status = None
"""The integer HTTP status code to emit."""
urls = None
"""The list of URL's to emit."""
encoding = 'utf-8'
"""The encoding when passed urls are not native strings"""
def __init__(self, urls, status=None, encoding=None):
import cherrypy
request = cherrypy.serving.request
if isinstance(urls, basestring):
urls = [urls]
abs_urls = []
for url in urls:
url = tonative(url, encoding or self.encoding)
# Note that urljoin will "do the right thing" whether url is:
# 1. a complete URL with host (e.g. "http://www.example.com/test")
# 2. a URL relative to root (e.g. "/dummy")
# 3. a URL relative to the current path
# Note that any query string in cherrypy.request is discarded.
url = _urljoin(cherrypy.url(), url)
abs_urls.append(url)
self.urls = abs_urls
# RFC 2616 indicates a 301 response code fits our goal; however,
# browser support for 301 is quite messy. Do 302/303 instead. See
# http://www.alanflavell.org.uk/www/post-redirect.html
if status is None:
if request.protocol >= (1, 1):
status = 303
else:
status = 302
else:
status = int(status)
if status < 300 or status > 399:
raise ValueError("status must be between 300 and 399.")
self.status = status
CherryPyException.__init__(self, abs_urls, status)
def set_response(self):
"""Modify cherrypy.response status, headers, and body to represent self.
CherryPy uses this internally, but you can also use it to create an
HTTPRedirect object and set its output without *raising* the exception.
"""
import cherrypy
response = cherrypy.serving.response
response.status = status = self.status
if status in (300, 301, 302, 303, 307):
response.headers['Content-Type'] = "text/html;charset=utf-8"
# "The ... URI SHOULD be given by the Location field
# in the response."
response.headers['Location'] = self.urls[0]
# "Unless the request method was HEAD, the entity of the response
# SHOULD contain a short hypertext note with a hyperlink to the
# new URI(s)."
msg = {300: "This resource can be found at <a href='%s'>%s</a>.",
301: "This resource has permanently moved to <a href='%s'>%s</a>.",
302: "This resource resides temporarily at <a href='%s'>%s</a>.",
303: "This resource can be found at <a href='%s'>%s</a>.",
307: "This resource has moved temporarily to <a href='%s'>%s</a>.",
}[status]
msgs = [msg % (u, u) for u in self.urls]
response.body = ntob("<br />\n".join(msgs), 'utf-8')
# Previous code may have set C-L, so we have to reset it
# (allow finalize to set it).
response.headers.pop('Content-Length', None)
elif status == 304:
# Not Modified.
# "The response MUST include the following header fields:
# Date, unless its omission is required by section 14.18.1"
# The "Date" header should have been set in Response.__init__
# "...the response SHOULD NOT include other entity-headers."
for key in ('Allow', 'Content-Encoding', 'Content-Language',
'Content-Length', 'Content-Location', 'Content-MD5',
'Content-Range', 'Content-Type', 'Expires',
'Last-Modified'):
if key in response.headers:
del response.headers[key]
# "The 304 response MUST NOT contain a message-body."
response.body = None
# Previous code may have set C-L, so we have to reset it.
response.headers.pop('Content-Length', None)
elif status == 305:
# Use Proxy.
# self.urls[0] should be the URI of the proxy.
response.headers['Location'] = self.urls[0]
response.body = None
# Previous code may have set C-L, so we have to reset it.
response.headers.pop('Content-Length', None)
else:
raise ValueError("The %s status code is unknown." % status)
def __call__(self):
"""Use this exception as a request.handler (raise self)."""
raise self
def clean_headers(status):
"""Remove any headers which should not apply to an error response."""
import cherrypy
response = cherrypy.serving.response
# Remove headers which applied to the original content,
# but do not apply to the error page.
respheaders = response.headers
for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After",
"Vary", "Content-Encoding", "Content-Length", "Expires",
"Content-Location", "Content-MD5", "Last-Modified"]:
if key in respheaders:
del respheaders[key]
if status != 416:
# A server sending a response with status code 416 (Requested
# range not satisfiable) SHOULD include a Content-Range field
# with a byte-range-resp-spec of "*". The instance-length
# specifies the current length of the selected resource.
# A response with status code 206 (Partial Content) MUST NOT
# include a Content-Range field with a byte-range- resp-spec of "*".
if "Content-Range" in respheaders:
del respheaders["Content-Range"]
class HTTPError(CherryPyException):
"""Exception used to return an HTTP error code (4xx-5xx) to the client.
This exception can be used to automatically send a response using a http status
code, with an appropriate error page. It takes an optional
``status`` argument (which must be between 400 and 599); it defaults to 500
("Internal Server Error"). It also takes an optional ``message`` argument,
which will be returned in the response body. See
`RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4>`_
for a complete list of available error codes and when to use them.
Examples::
raise cherrypy.HTTPError(403)
raise cherrypy.HTTPError("403 Forbidden", "You are not allowed to access this resource.")
"""
status = None
"""The HTTP status code. May be of type int or str (with a Reason-Phrase)."""
code = None
"""The integer HTTP status code."""
reason = None
"""The HTTP Reason-Phrase string."""
def __init__(self, status=500, message=None):
self.status = status
try:
self.code, self.reason, defaultmsg = _httputil.valid_status(status)
except ValueError:
raise self.__class__(500, _exc_info()[1].args[0])
if self.code < 400 or self.code > 599:
raise ValueError("status must be between 400 and 599.")
# See http://www.python.org/dev/peps/pep-0352/
# self.message = message
self._message = message or defaultmsg
CherryPyException.__init__(self, status, message)
def set_response(self):
"""Modify cherrypy.response status, headers, and body to represent self.
CherryPy uses this internally, but you can also use it to create an
HTTPError object and set its output without *raising* the exception.
"""
import cherrypy
response = cherrypy.serving.response
clean_headers(self.code)
# In all cases, finalize will be called after this method,
# so don't bother cleaning up response values here.
response.status = self.status
tb = None
if cherrypy.serving.request.show_tracebacks:
tb = format_exc()
response.headers['Content-Type'] = "text/html;charset=utf-8"
response.headers.pop('Content-Length', None)
content = ntob(self.get_error_page(self.status, traceback=tb,
message=self._message), 'utf-8')
response.body = content
_be_ie_unfriendly(self.code)
def get_error_page(self, *args, **kwargs):
return get_error_page(*args, **kwargs)
def __call__(self):
"""Use this exception as a request.handler (raise self)."""
raise self
class NotFound(HTTPError):
"""Exception raised when a URL could not be mapped to any handler (404).
This is equivalent to raising
:class:`HTTPError("404 Not Found") <cherrypy._cperror.HTTPError>`.
"""
def __init__(self, path=None):
if path is None:
import cherrypy
request = cherrypy.serving.request
path = request.script_name + request.path_info
self.args = (path,)
HTTPError.__init__(self, 404, "The path '%s' was not found." % path)
_HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
<title>%(status)s</title>
<style type="text/css">
#powered_by {
margin-top: 20px;
border-top: 2px solid black;
font-style: italic;
}
#traceback {
color: red;
}
</style>
</head>
<body>
<h2>%(status)s</h2>
<p>%(message)s</p>
<pre id="traceback">%(traceback)s</pre>
<div id="powered_by">
<span>Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a></span>
</div>
</body>
</html>
'''
def get_error_page(status, **kwargs):
"""Return an HTML page, containing a pretty error response.
status should be an int or a str.
kwargs will be interpolated into the page template.
"""
import cherrypy
try:
code, reason, message = _httputil.valid_status(status)
except ValueError:
raise cherrypy.HTTPError(500, _exc_info()[1].args[0])
# We can't use setdefault here, because some
# callers send None for kwarg values.
if kwargs.get('status') is None:
kwargs['status'] = "%s %s" % (code, reason)
if kwargs.get('message') is None:
kwargs['message'] = message
if kwargs.get('traceback') is None:
kwargs['traceback'] = ''
if kwargs.get('version') is None:
kwargs['version'] = cherrypy.__version__
for k, v in iteritems(kwargs):
if v is None:
kwargs[k] = ""
else:
kwargs[k] = _escape(kwargs[k])
# Use a custom template or callable for the error page?
pages = cherrypy.serving.request.error_page
error_page = pages.get(code) or pages.get('default')
if error_page:
try:
if hasattr(error_page, '__call__'):
return error_page(**kwargs)
else:
data = open(error_page, 'rb').read()
return tonative(data) % kwargs
except:
e = _format_exception(*_exc_info())[-1]
m = kwargs['message']
if m:
m += "<br />"
m += "In addition, the custom error page failed:\n<br />%s" % e
kwargs['message'] = m
return _HTTPErrorTemplate % kwargs
_ie_friendly_error_sizes = {
400: 512, 403: 256, 404: 512, 405: 256,
406: 512, 408: 512, 409: 512, 410: 256,
500: 512, 501: 512, 505: 512,
}
def _be_ie_unfriendly(status):
import cherrypy
response = cherrypy.serving.response
# For some statuses, Internet Explorer 5+ shows "friendly error
# messages" instead of our response.body if the body is smaller
# than a given size. Fix this by returning a body over that size
# (by adding whitespace).
# See http://support.microsoft.com/kb/q218155/
s = _ie_friendly_error_sizes.get(status, 0)
if s:
s += 1
# Since we are issuing an HTTP error status, we assume that
# the entity is short, and we should just collapse it.
content = response.collapse_body()
l = len(content)
if l and l < s:
# IN ADDITION: the response must be written to IE
# in one chunk or it will still get replaced! Bah.
content = content + (ntob(" ") * (s - l))
response.body = content
response.headers['Content-Length'] = str(len(content))
def format_exc(exc=None):
"""Return exc (or sys.exc_info if None), formatted."""
try:
if exc is None:
exc = _exc_info()
if exc == (None, None, None):
return ""
import traceback
return "".join(traceback.format_exception(*exc))
finally:
del exc
def bare_error(extrabody=None):
"""Produce status, headers, body for a critical error.
Returns a triple without calling any other questionable functions,
so it should be as error-free as possible. Call it from an HTTP server
if you get errors outside of the request.
If extrabody is None, a friendly but rather unhelpful error message
is set in the body. If extrabody is a string, it will be appended
as-is to the body.
"""
# The whole point of this function is to be a last line-of-defense
# in handling errors. That is, it must not raise any errors itself;
# it cannot be allowed to fail. Therefore, don't add to it!
# In particular, don't call any other CP functions.
body = ntob("Unrecoverable error in the server.")
if extrabody is not None:
if not isinstance(extrabody, bytestr):
extrabody = extrabody.encode('utf-8')
body += ntob("\n") + extrabody
return (ntob("500 Internal Server Error"),
[(ntob('Content-Type'), ntob('text/plain')),
(ntob('Content-Length'), ntob(str(len(body)),'ISO-8859-1'))],
[body])

View File

@ -1,440 +0,0 @@
"""
Simple config
=============
Although CherryPy uses the :mod:`Python logging module <logging>`, it does so
behind the scenes so that simple logging is simple, but complicated logging
is still possible. "Simple" logging means that you can log to the screen
(i.e. console/stdout) or to a file, and that you can easily have separate
error and access log files.
Here are the simplified logging settings. You use these by adding lines to
your config file or dict. You should set these at either the global level or
per application (see next), but generally not both.
* ``log.screen``: Set this to True to have both "error" and "access" messages
printed to stdout.
* ``log.access_file``: Set this to an absolute filename where you want
"access" messages written.
* ``log.error_file``: Set this to an absolute filename where you want "error"
messages written.
Many events are automatically logged; to log your own application events, call
:func:`cherrypy.log`.
Architecture
============
Separate scopes
---------------
CherryPy provides log managers at both the global and application layers.
This means you can have one set of logging rules for your entire site,
and another set of rules specific to each application. The global log
manager is found at :func:`cherrypy.log`, and the log manager for each
application is found at :attr:`app.log<cherrypy._cptree.Application.log>`.
If you're inside a request, the latter is reachable from
``cherrypy.request.app.log``; if you're outside a request, you'll have to obtain
a reference to the ``app``: either the return value of
:func:`tree.mount()<cherrypy._cptree.Tree.mount>` or, if you used
:func:`quickstart()<cherrypy.quickstart>` instead, via ``cherrypy.tree.apps['/']``.
By default, the global logs are named "cherrypy.error" and "cherrypy.access",
and the application logs are named "cherrypy.error.2378745" and
"cherrypy.access.2378745" (the number is the id of the Application object).
This means that the application logs "bubble up" to the site logs, so if your
application has no log handlers, the site-level handlers will still log the
messages.
Errors vs. Access
-----------------
Each log manager handles both "access" messages (one per HTTP request) and
"error" messages (everything else). Note that the "error" log is not just for
errors! The format of access messages is highly formalized, but the error log
isn't--it receives messages from a variety of sources (including full error
tracebacks, if enabled).
Custom Handlers
===============
The simple settings above work by manipulating Python's standard :mod:`logging`
module. So when you need something more complex, the full power of the standard
module is yours to exploit. You can borrow or create custom handlers, formats,
filters, and much more. Here's an example that skips the standard FileHandler
and uses a RotatingFileHandler instead:
::
#python
log = app.log
# Remove the default FileHandlers if present.
log.error_file = ""
log.access_file = ""
maxBytes = getattr(log, "rot_maxBytes", 10000000)
backupCount = getattr(log, "rot_backupCount", 1000)
# Make a new RotatingFileHandler for the error log.
fname = getattr(log, "rot_error_file", "error.log")
h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
h.setLevel(DEBUG)
h.setFormatter(_cplogging.logfmt)
log.error_log.addHandler(h)
# Make a new RotatingFileHandler for the access log.
fname = getattr(log, "rot_access_file", "access.log")
h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
h.setLevel(DEBUG)
h.setFormatter(_cplogging.logfmt)
log.access_log.addHandler(h)
The ``rot_*`` attributes are pulled straight from the application log object.
Since "log.*" config entries simply set attributes on the log object, you can
add custom attributes to your heart's content. Note that these handlers are
used ''instead'' of the default, simple handlers outlined above (so don't set
the "log.error_file" config entry, for example).
"""
import datetime
import logging
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
logging.Logger.manager.emittedNoHandlerWarning = 1
logfmt = logging.Formatter("%(message)s")
import os
import sys
import cherrypy
from cherrypy import _cperror
from cherrypy._cpcompat import ntob, py3k
class NullHandler(logging.Handler):
"""A no-op logging handler to silence the logging.lastResort handler."""
def handle(self, record):
pass
def emit(self, record):
pass
def createLock(self):
self.lock = None
class LogManager(object):
"""An object to assist both simple and advanced logging.
``cherrypy.log`` is an instance of this class.
"""
appid = None
"""The id() of the Application object which owns this log manager. If this
is a global log manager, appid is None."""
error_log = None
"""The actual :class:`logging.Logger` instance for error messages."""
access_log = None
"""The actual :class:`logging.Logger` instance for access messages."""
if py3k:
access_log_format = \
'{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
else:
access_log_format = \
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
logger_root = None
"""The "top-level" logger name.
This string will be used as the first segment in the Logger names.
The default is "cherrypy", for example, in which case the Logger names
will be of the form::
cherrypy.error.<appid>
cherrypy.access.<appid>
"""
def __init__(self, appid=None, logger_root="cherrypy"):
self.logger_root = logger_root
self.appid = appid
if appid is None:
self.error_log = logging.getLogger("%s.error" % logger_root)
self.access_log = logging.getLogger("%s.access" % logger_root)
else:
self.error_log = logging.getLogger("%s.error.%s" % (logger_root, appid))
self.access_log = logging.getLogger("%s.access.%s" % (logger_root, appid))
self.error_log.setLevel(logging.INFO)
self.access_log.setLevel(logging.INFO)
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
self.error_log.addHandler(NullHandler())
self.access_log.addHandler(NullHandler())
cherrypy.engine.subscribe('graceful', self.reopen_files)
def reopen_files(self):
"""Close and reopen all file handlers."""
for log in (self.error_log, self.access_log):
for h in log.handlers:
if isinstance(h, logging.FileHandler):
h.acquire()
h.stream.close()
h.stream = open(h.baseFilename, h.mode)
h.release()
def error(self, msg='', context='', severity=logging.INFO, traceback=False):
"""Write the given ``msg`` to the error log.
This is not just for errors! Applications may call this at any time
to log application-specific information.
If ``traceback`` is True, the traceback of the current exception
(if any) will be appended to ``msg``.
"""
if traceback:
msg += _cperror.format_exc()
self.error_log.log(severity, ' '.join((self.time(), context, msg)))
def __call__(self, *args, **kwargs):
"""An alias for ``error``."""
return self.error(*args, **kwargs)
def access(self):
"""Write to the access log (in Apache/NCSA Combined Log format).
See http://httpd.apache.org/docs/2.0/logs.html#combined for format
details.
CherryPy calls this automatically for you. Note there are no arguments;
it collects the data itself from
:class:`cherrypy.request<cherrypy._cprequest.Request>`.
Like Apache started doing in 2.0.46, non-printable and other special
characters in %r (and we expand that to all parts) are escaped using
\\xhh sequences, where hh stands for the hexadecimal representation
of the raw byte. Exceptions from this rule are " and \\, which are
escaped by prepending a backslash, and all whitespace characters,
which are written in their C-style notation (\\n, \\t, etc).
"""
request = cherrypy.serving.request
remote = request.remote
response = cherrypy.serving.response
outheaders = response.headers
inheaders = request.headers
if response.output_status is None:
status = "-"
else:
status = response.output_status.split(ntob(" "), 1)[0]
if py3k:
status = status.decode('ISO-8859-1')
atoms = {'h': remote.name or remote.ip,
'l': '-',
'u': getattr(request, "login", None) or "-",
't': self.time(),
'r': request.request_line,
's': status,
'b': dict.get(outheaders, 'Content-Length', '') or "-",
'f': dict.get(inheaders, 'Referer', ''),
'a': dict.get(inheaders, 'User-Agent', ''),
}
if py3k:
for k, v in atoms.items():
if not isinstance(v, str):
v = str(v)
v = v.replace('"', '\\"').encode('utf8')
# Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
# and backslash for us. All we have to do is strip the quotes.
v = repr(v)[2:-1]
# in python 3.0 the repr of bytes (as returned by encode)
# uses double \'s. But then the logger escapes them yet, again
# resulting in quadruple slashes. Remove the extra one here.
v = v.replace('\\\\', '\\')
# Escape double-quote.
atoms[k] = v
try:
self.access_log.log(logging.INFO, self.access_log_format.format(**atoms))
except:
self(traceback=True)
else:
for k, v in atoms.items():
if isinstance(v, unicode):
v = v.encode('utf8')
elif not isinstance(v, str):
v = str(v)
# Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
# and backslash for us. All we have to do is strip the quotes.
v = repr(v)[1:-1]
# Escape double-quote.
atoms[k] = v.replace('"', '\\"')
try:
self.access_log.log(logging.INFO, self.access_log_format % atoms)
except:
self(traceback=True)
def time(self):
"""Return now() in Apache Common Log Format (no timezone)."""
now = datetime.datetime.now()
monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
month = monthnames[now.month - 1].capitalize()
return ('[%02d/%s/%04d:%02d:%02d:%02d]' %
(now.day, month, now.year, now.hour, now.minute, now.second))
def _get_builtin_handler(self, log, key):
for h in log.handlers:
if getattr(h, "_cpbuiltin", None) == key:
return h
# ------------------------- Screen handlers ------------------------- #
def _set_screen_handler(self, log, enable, stream=None):
h = self._get_builtin_handler(log, "screen")
if enable:
if not h:
if stream is None:
stream=sys.stderr
h = logging.StreamHandler(stream)
h.setFormatter(logfmt)
h._cpbuiltin = "screen"
log.addHandler(h)
elif h:
log.handlers.remove(h)
def _get_screen(self):
h = self._get_builtin_handler
has_h = h(self.error_log, "screen") or h(self.access_log, "screen")
return bool(has_h)
def _set_screen(self, newvalue):
self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr)
self._set_screen_handler(self.access_log, newvalue, stream=sys.stdout)
screen = property(_get_screen, _set_screen,
doc="""Turn stderr/stdout logging on or off.
If you set this to True, it'll add the appropriate StreamHandler for
you. If you set it to False, it will remove the handler.
""")
# -------------------------- File handlers -------------------------- #
def _add_builtin_file_handler(self, log, fname):
h = logging.FileHandler(fname)
h.setFormatter(logfmt)
h._cpbuiltin = "file"
log.addHandler(h)
def _set_file_handler(self, log, filename):
h = self._get_builtin_handler(log, "file")
if filename:
if h:
if h.baseFilename != os.path.abspath(filename):
h.close()
log.handlers.remove(h)
self._add_builtin_file_handler(log, filename)
else:
self._add_builtin_file_handler(log, filename)
else:
if h:
h.close()
log.handlers.remove(h)
def _get_error_file(self):
h = self._get_builtin_handler(self.error_log, "file")
if h:
return h.baseFilename
return ''
def _set_error_file(self, newvalue):
self._set_file_handler(self.error_log, newvalue)
error_file = property(_get_error_file, _set_error_file,
doc="""The filename for self.error_log.
If you set this to a string, it'll add the appropriate FileHandler for
you. If you set it to ``None`` or ``''``, it will remove the handler.
""")
def _get_access_file(self):
h = self._get_builtin_handler(self.access_log, "file")
if h:
return h.baseFilename
return ''
def _set_access_file(self, newvalue):
self._set_file_handler(self.access_log, newvalue)
access_file = property(_get_access_file, _set_access_file,
doc="""The filename for self.access_log.
If you set this to a string, it'll add the appropriate FileHandler for
you. If you set it to ``None`` or ``''``, it will remove the handler.
""")
# ------------------------- WSGI handlers ------------------------- #
def _set_wsgi_handler(self, log, enable):
h = self._get_builtin_handler(log, "wsgi")
if enable:
if not h:
h = WSGIErrorHandler()
h.setFormatter(logfmt)
h._cpbuiltin = "wsgi"
log.addHandler(h)
elif h:
log.handlers.remove(h)
def _get_wsgi(self):
return bool(self._get_builtin_handler(self.error_log, "wsgi"))
def _set_wsgi(self, newvalue):
self._set_wsgi_handler(self.error_log, newvalue)
wsgi = property(_get_wsgi, _set_wsgi,
doc="""Write errors to wsgi.errors.
If you set this to True, it'll add the appropriate
:class:`WSGIErrorHandler<cherrypy._cplogging.WSGIErrorHandler>` for you
(which writes errors to ``wsgi.errors``).
If you set it to False, it will remove the handler.
""")
class WSGIErrorHandler(logging.Handler):
"A handler class which writes logging records to environ['wsgi.errors']."
def flush(self):
"""Flushes the stream."""
try:
stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
except (AttributeError, KeyError):
pass
else:
stream.flush()
def emit(self, record):
"""Emit a record."""
try:
stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors')
except (AttributeError, KeyError):
pass
else:
try:
msg = self.format(record)
fs = "%s\n"
import types
if not hasattr(types, "UnicodeType"): #if no unicode support...
stream.write(fs % msg)
else:
try:
stream.write(fs % msg)
except UnicodeError:
stream.write(fs % msg.encode("UTF-8"))
self.flush()
except:
self.handleError(record)

View File

@ -1,344 +0,0 @@
"""Native adapter for serving CherryPy via mod_python
Basic usage:
##########################################
# Application in a module called myapp.py
##########################################
import cherrypy
class Root:
@cherrypy.expose
def index(self):
return 'Hi there, Ho there, Hey there'
# We will use this method from the mod_python configuration
# as the entry point to our application
def setup_server():
cherrypy.tree.mount(Root())
cherrypy.config.update({'environment': 'production',
'log.screen': False,
'show_tracebacks': False})
##########################################
# mod_python settings for apache2
# This should reside in your httpd.conf
# or a file that will be loaded at
# apache startup
##########################################
# Start
DocumentRoot "/"
Listen 8080
LoadModule python_module /usr/lib/apache2/modules/mod_python.so
<Location "/">
PythonPath "sys.path+['/path/to/my/application']"
SetHandler python-program
PythonHandler cherrypy._cpmodpy::handler
PythonOption cherrypy.setup myapp::setup_server
PythonDebug On
</Location>
# End
The actual path to your mod_python.so is dependent on your
environment. In this case we suppose a global mod_python
installation on a Linux distribution such as Ubuntu.
We do set the PythonPath configuration setting so that
your application can be found by from the user running
the apache2 instance. Of course if your application
resides in the global site-package this won't be needed.
Then restart apache2 and access http://127.0.0.1:8080
"""
import logging
import sys
import cherrypy
from cherrypy._cpcompat import BytesIO, copyitems, ntob
from cherrypy._cperror import format_exc, bare_error
from cherrypy.lib import httputil
# ------------------------------ Request-handling
def setup(req):
from mod_python import apache
# Run any setup functions defined by a "PythonOption cherrypy.setup" directive.
options = req.get_options()
if 'cherrypy.setup' in options:
for function in options['cherrypy.setup'].split():
atoms = function.split('::', 1)
if len(atoms) == 1:
mod = __import__(atoms[0], globals(), locals())
else:
modname, fname = atoms
mod = __import__(modname, globals(), locals(), [fname])
func = getattr(mod, fname)
func()
cherrypy.config.update({'log.screen': False,
"tools.ignore_headers.on": True,
"tools.ignore_headers.headers": ['Range'],
})
engine = cherrypy.engine
if hasattr(engine, "signal_handler"):
engine.signal_handler.unsubscribe()
if hasattr(engine, "console_control_handler"):
engine.console_control_handler.unsubscribe()
engine.autoreload.unsubscribe()
cherrypy.server.unsubscribe()
def _log(msg, level):
newlevel = apache.APLOG_ERR
if logging.DEBUG >= level:
newlevel = apache.APLOG_DEBUG
elif logging.INFO >= level:
newlevel = apache.APLOG_INFO
elif logging.WARNING >= level:
newlevel = apache.APLOG_WARNING
# On Windows, req.server is required or the msg will vanish. See
# http://www.modpython.org/pipermail/mod_python/2003-October/014291.html.
# Also, "When server is not specified...LogLevel does not apply..."
apache.log_error(msg, newlevel, req.server)
engine.subscribe('log', _log)
engine.start()
def cherrypy_cleanup(data):
engine.exit()
try:
# apache.register_cleanup wasn't available until 3.1.4.
apache.register_cleanup(cherrypy_cleanup)
except AttributeError:
req.server.register_cleanup(req, cherrypy_cleanup)
class _ReadOnlyRequest:
expose = ('read', 'readline', 'readlines')
def __init__(self, req):
for method in self.expose:
self.__dict__[method] = getattr(req, method)
recursive = False
_isSetUp = False
def handler(req):
from mod_python import apache
try:
global _isSetUp
if not _isSetUp:
setup(req)
_isSetUp = True
# Obtain a Request object from CherryPy
local = req.connection.local_addr
local = httputil.Host(local[0], local[1], req.connection.local_host or "")
remote = req.connection.remote_addr
remote = httputil.Host(remote[0], remote[1], req.connection.remote_host or "")
scheme = req.parsed_uri[0] or 'http'
req.get_basic_auth_pw()
try:
# apache.mpm_query only became available in mod_python 3.1
q = apache.mpm_query
threaded = q(apache.AP_MPMQ_IS_THREADED)
forked = q(apache.AP_MPMQ_IS_FORKED)
except AttributeError:
bad_value = ("You must provide a PythonOption '%s', "
"either 'on' or 'off', when running a version "
"of mod_python < 3.1")
threaded = options.get('multithread', '').lower()
if threaded == 'on':
threaded = True
elif threaded == 'off':
threaded = False
else:
raise ValueError(bad_value % "multithread")
forked = options.get('multiprocess', '').lower()
if forked == 'on':
forked = True
elif forked == 'off':
forked = False
else:
raise ValueError(bad_value % "multiprocess")
sn = cherrypy.tree.script_name(req.uri or "/")
if sn is None:
send_response(req, '404 Not Found', [], '')
else:
app = cherrypy.tree.apps[sn]
method = req.method
path = req.uri
qs = req.args or ""
reqproto = req.protocol
headers = copyitems(req.headers_in)
rfile = _ReadOnlyRequest(req)
prev = None
try:
redirections = []
while True:
request, response = app.get_serving(local, remote, scheme,
"HTTP/1.1")
request.login = req.user
request.multithread = bool(threaded)
request.multiprocess = bool(forked)
request.app = app
request.prev = prev
# Run the CherryPy Request object and obtain the response
try:
request.run(method, path, qs, reqproto, headers, rfile)
break
except cherrypy.InternalRedirect:
ir = sys.exc_info()[1]
app.release_serving()
prev = request
if not recursive:
if ir.path in redirections:
raise RuntimeError("InternalRedirector visited the "
"same URL twice: %r" % ir.path)
else:
# Add the *previous* path_info + qs to redirections.
if qs:
qs = "?" + qs
redirections.append(sn + path + qs)
# Munge environment and try again.
method = "GET"
path = ir.path
qs = ir.query_string
rfile = BytesIO()
send_response(req, response.output_status, response.header_list,
response.body, response.stream)
finally:
app.release_serving()
except:
tb = format_exc()
cherrypy.log(tb, 'MOD_PYTHON', severity=logging.ERROR)
s, h, b = bare_error()
send_response(req, s, h, b)
return apache.OK
def send_response(req, status, headers, body, stream=False):
# Set response status
req.status = int(status[:3])
# Set response headers
req.content_type = "text/plain"
for header, value in headers:
if header.lower() == 'content-type':
req.content_type = value
continue
req.headers_out.add(header, value)
if stream:
# Flush now so the status and headers are sent immediately.
req.flush()
# Set response body
if isinstance(body, basestring):
req.write(body)
else:
for seg in body:
req.write(seg)
# --------------- Startup tools for CherryPy + mod_python --------------- #
import os
import re
try:
import subprocess
def popen(fullcmd):
p = subprocess.Popen(fullcmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
close_fds=True)
return p.stdout
except ImportError:
def popen(fullcmd):
pipein, pipeout = os.popen4(fullcmd)
return pipeout
def read_process(cmd, args=""):
fullcmd = "%s %s" % (cmd, args)
pipeout = popen(fullcmd)
try:
firstline = pipeout.readline()
if (re.search(ntob("(not recognized|No such file|not found)"), firstline,
re.IGNORECASE)):
raise IOError('%s must be on your system path.' % cmd)
output = firstline + pipeout.read()
finally:
pipeout.close()
return output
class ModPythonServer(object):
template = """
# Apache2 server configuration file for running CherryPy with mod_python.
DocumentRoot "/"
Listen %(port)s
LoadModule python_module modules/mod_python.so
<Location %(loc)s>
SetHandler python-program
PythonHandler %(handler)s
PythonDebug On
%(opts)s
</Location>
"""
def __init__(self, loc="/", port=80, opts=None, apache_path="apache",
handler="cherrypy._cpmodpy::handler"):
self.loc = loc
self.port = port
self.opts = opts
self.apache_path = apache_path
self.handler = handler
def start(self):
opts = "".join([" PythonOption %s %s\n" % (k, v)
for k, v in self.opts])
conf_data = self.template % {"port": self.port,
"loc": self.loc,
"opts": opts,
"handler": self.handler,
}
mpconf = os.path.join(os.path.dirname(__file__), "cpmodpy.conf")
f = open(mpconf, 'wb')
try:
f.write(conf_data)
finally:
f.close()
response = read_process(self.apache_path, "-k start -f %s" % mpconf)
self.ready = True
return response
def stop(self):
os.popen("apache -k stop")
self.ready = False

View File

@ -1,149 +0,0 @@
"""Native adapter for serving CherryPy via its builtin server."""
import logging
import sys
import cherrypy
from cherrypy._cpcompat import BytesIO
from cherrypy._cperror import format_exc, bare_error
from cherrypy.lib import httputil
from cherrypy import wsgiserver
class NativeGateway(wsgiserver.Gateway):
recursive = False
def respond(self):
req = self.req
try:
# Obtain a Request object from CherryPy
local = req.server.bind_addr
local = httputil.Host(local[0], local[1], "")
remote = req.conn.remote_addr, req.conn.remote_port
remote = httputil.Host(remote[0], remote[1], "")
scheme = req.scheme
sn = cherrypy.tree.script_name(req.uri or "/")
if sn is None:
self.send_response('404 Not Found', [], [''])
else:
app = cherrypy.tree.apps[sn]
method = req.method
path = req.path
qs = req.qs or ""
headers = req.inheaders.items()
rfile = req.rfile
prev = None
try:
redirections = []
while True:
request, response = app.get_serving(
local, remote, scheme, "HTTP/1.1")
request.multithread = True
request.multiprocess = False
request.app = app
request.prev = prev
# Run the CherryPy Request object and obtain the response
try:
request.run(method, path, qs, req.request_protocol, headers, rfile)
break
except cherrypy.InternalRedirect:
ir = sys.exc_info()[1]
app.release_serving()
prev = request
if not self.recursive:
if ir.path in redirections:
raise RuntimeError("InternalRedirector visited the "
"same URL twice: %r" % ir.path)
else:
# Add the *previous* path_info + qs to redirections.
if qs:
qs = "?" + qs
redirections.append(sn + path + qs)
# Munge environment and try again.
method = "GET"
path = ir.path
qs = ir.query_string
rfile = BytesIO()
self.send_response(
response.output_status, response.header_list,
response.body)
finally:
app.release_serving()
except:
tb = format_exc()
#print tb
cherrypy.log(tb, 'NATIVE_ADAPTER', severity=logging.ERROR)
s, h, b = bare_error()
self.send_response(s, h, b)
def send_response(self, status, headers, body):
req = self.req
# Set response status
req.status = str(status or "500 Server Error")
# Set response headers
for header, value in headers:
req.outheaders.append((header, value))
if (req.ready and not req.sent_headers):
req.sent_headers = True
req.send_headers()
# Set response body
for seg in body:
req.write(seg)
class CPHTTPServer(wsgiserver.HTTPServer):
"""Wrapper for wsgiserver.HTTPServer.
wsgiserver has been designed to not reference CherryPy in any way,
so that it can be used in other frameworks and applications.
Therefore, we wrap it here, so we can apply some attributes
from config -> cherrypy.server -> HTTPServer.
"""
def __init__(self, server_adapter=cherrypy.server):
self.server_adapter = server_adapter
server_name = (self.server_adapter.socket_host or
self.server_adapter.socket_file or
None)
wsgiserver.HTTPServer.__init__(
self, server_adapter.bind_addr, NativeGateway,
minthreads=server_adapter.thread_pool,
maxthreads=server_adapter.thread_pool_max,
server_name=server_name)
self.max_request_header_size = self.server_adapter.max_request_header_size or 0
self.max_request_body_size = self.server_adapter.max_request_body_size or 0
self.request_queue_size = self.server_adapter.socket_queue_size
self.timeout = self.server_adapter.socket_timeout
self.shutdown_timeout = self.server_adapter.shutdown_timeout
self.protocol = self.server_adapter.protocol_version
self.nodelay = self.server_adapter.nodelay
ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
if self.server_adapter.ssl_context:
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
self.ssl_adapter = adapter_class(
self.server_adapter.ssl_certificate,
self.server_adapter.ssl_private_key,
self.server_adapter.ssl_certificate_chain)
self.ssl_adapter.context = self.server_adapter.ssl_context
elif self.server_adapter.ssl_certificate:
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
self.ssl_adapter = adapter_class(
self.server_adapter.ssl_certificate,
self.server_adapter.ssl_private_key,
self.server_adapter.ssl_certificate_chain)

View File

@ -1,965 +0,0 @@
"""Request body processing for CherryPy.
.. versionadded:: 3.2
Application authors have complete control over the parsing of HTTP request
entities. In short, :attr:`cherrypy.request.body<cherrypy._cprequest.Request.body>`
is now always set to an instance of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>`,
and *that* class is a subclass of :class:`Entity<cherrypy._cpreqbody.Entity>`.
When an HTTP request includes an entity body, it is often desirable to
provide that information to applications in a form other than the raw bytes.
Different content types demand different approaches. Examples:
* For a GIF file, we want the raw bytes in a stream.
* An HTML form is better parsed into its component fields, and each text field
decoded from bytes to unicode.
* A JSON body should be deserialized into a Python dict or list.
When the request contains a Content-Type header, the media type is used as a
key to look up a value in the
:attr:`request.body.processors<cherrypy._cpreqbody.Entity.processors>` dict.
If the full media
type is not found, then the major type is tried; for example, if no processor
is found for the 'image/jpeg' type, then we look for a processor for the 'image'
types altogether. If neither the full type nor the major type has a matching
processor, then a default processor is used
(:func:`default_proc<cherrypy._cpreqbody.Entity.default_proc>`). For most
types, this means no processing is done, and the body is left unread as a
raw byte stream. Processors are configurable in an 'on_start_resource' hook.
Some processors, especially those for the 'text' types, attempt to decode bytes
to unicode. If the Content-Type request header includes a 'charset' parameter,
this is used to decode the entity. Otherwise, one or more default charsets may
be attempted, although this decision is up to each processor. If a processor
successfully decodes an Entity or Part, it should set the
:attr:`charset<cherrypy._cpreqbody.Entity.charset>` attribute
on the Entity or Part to the name of the successful charset, so that
applications can easily re-encode or transcode the value if they wish.
If the Content-Type of the request entity is of major type 'multipart', then
the above parsing process, and possibly a decoding process, is performed for
each part.
For both the full entity and multipart parts, a Content-Disposition header may
be used to fill :attr:`name<cherrypy._cpreqbody.Entity.name>` and
:attr:`filename<cherrypy._cpreqbody.Entity.filename>` attributes on the
request.body or the Part.
.. _custombodyprocessors:
Custom Processors
=================
You can add your own processors for any specific or major MIME type. Simply add
it to the :attr:`processors<cherrypy._cprequest.Entity.processors>` dict in a
hook/tool that runs at ``on_start_resource`` or ``before_request_body``.
Here's the built-in JSON tool for an example::
def json_in(force=True, debug=False):
request = cherrypy.serving.request
def json_processor(entity):
\"""Read application/json data into request.json.\"""
if not entity.headers.get("Content-Length", ""):
raise cherrypy.HTTPError(411)
body = entity.fp.read()
try:
request.json = json_decode(body)
except ValueError:
raise cherrypy.HTTPError(400, 'Invalid JSON document')
if force:
request.body.processors.clear()
request.body.default_proc = cherrypy.HTTPError(
415, 'Expected an application/json content type')
request.body.processors['application/json'] = json_processor
We begin by defining a new ``json_processor`` function to stick in the ``processors``
dictionary. All processor functions take a single argument, the ``Entity`` instance
they are to process. It will be called whenever a request is received (for those
URI's where the tool is turned on) which has a ``Content-Type`` of
"application/json".
First, it checks for a valid ``Content-Length`` (raising 411 if not valid), then
reads the remaining bytes on the socket. The ``fp`` object knows its own length, so
it won't hang waiting for data that never arrives. It will return when all data
has been read. Then, we decode those bytes using Python's built-in ``json`` module,
and stick the decoded result onto ``request.json`` . If it cannot be decoded, we
raise 400.
If the "force" argument is True (the default), the ``Tool`` clears the ``processors``
dict so that request entities of other ``Content-Types`` aren't parsed at all. Since
there's no entry for those invalid MIME types, the ``default_proc`` method of ``cherrypy.request.body``
is called. But this does nothing by default (usually to provide the page handler an opportunity to handle it.)
But in our case, we want to raise 415, so we replace ``request.body.default_proc``
with the error (``HTTPError`` instances, when called, raise themselves).
If we were defining a custom processor, we can do so without making a ``Tool``. Just add the config entry::
request.body.processors = {'application/json': json_processor}
Note that you can only replace the ``processors`` dict wholesale this way, not update the existing one.
"""
try:
from io import DEFAULT_BUFFER_SIZE
except ImportError:
DEFAULT_BUFFER_SIZE = 8192
import re
import sys
import tempfile
try:
from urllib import unquote_plus
except ImportError:
def unquote_plus(bs):
"""Bytes version of urllib.parse.unquote_plus."""
bs = bs.replace(ntob('+'), ntob(' '))
atoms = bs.split(ntob('%'))
for i in range(1, len(atoms)):
item = atoms[i]
try:
pct = int(item[:2], 16)
atoms[i] = bytes([pct]) + item[2:]
except ValueError:
pass
return ntob('').join(atoms)
import cherrypy
from cherrypy._cpcompat import basestring, ntob, ntou
from cherrypy.lib import httputil
# -------------------------------- Processors -------------------------------- #
def process_urlencoded(entity):
"""Read application/x-www-form-urlencoded data into entity.params."""
qs = entity.fp.read()
for charset in entity.attempt_charsets:
try:
params = {}
for aparam in qs.split(ntob('&')):
for pair in aparam.split(ntob(';')):
if not pair:
continue
atoms = pair.split(ntob('='), 1)
if len(atoms) == 1:
atoms.append(ntob(''))
key = unquote_plus(atoms[0]).decode(charset)
value = unquote_plus(atoms[1]).decode(charset)
if key in params:
if not isinstance(params[key], list):
params[key] = [params[key]]
params[key].append(value)
else:
params[key] = value
except UnicodeDecodeError:
pass
else:
entity.charset = charset
break
else:
raise cherrypy.HTTPError(
400, "The request entity could not be decoded. The following "
"charsets were attempted: %s" % repr(entity.attempt_charsets))
# Now that all values have been successfully parsed and decoded,
# apply them to the entity.params dict.
for key, value in params.items():
if key in entity.params:
if not isinstance(entity.params[key], list):
entity.params[key] = [entity.params[key]]
entity.params[key].append(value)
else:
entity.params[key] = value
def process_multipart(entity):
"""Read all multipart parts into entity.parts."""
ib = ""
if 'boundary' in entity.content_type.params:
# http://tools.ietf.org/html/rfc2046#section-5.1.1
# "The grammar for parameters on the Content-type field is such that it
# is often necessary to enclose the boundary parameter values in quotes
# on the Content-type line"
ib = entity.content_type.params['boundary'].strip('"')
if not re.match("^[ -~]{0,200}[!-~]$", ib):
raise ValueError('Invalid boundary in multipart form: %r' % (ib,))
ib = ('--' + ib).encode('ascii')
# Find the first marker
while True:
b = entity.readline()
if not b:
return
b = b.strip()
if b == ib:
break
# Read all parts
while True:
part = entity.part_class.from_fp(entity.fp, ib)
entity.parts.append(part)
part.process()
if part.fp.done:
break
def process_multipart_form_data(entity):
"""Read all multipart/form-data parts into entity.parts or entity.params."""
process_multipart(entity)
kept_parts = []
for part in entity.parts:
if part.name is None:
kept_parts.append(part)
else:
if part.filename is None:
# It's a regular field
value = part.fullvalue()
else:
# It's a file upload. Retain the whole part so consumer code
# has access to its .file and .filename attributes.
value = part
if part.name in entity.params:
if not isinstance(entity.params[part.name], list):
entity.params[part.name] = [entity.params[part.name]]
entity.params[part.name].append(value)
else:
entity.params[part.name] = value
entity.parts = kept_parts
def _old_process_multipart(entity):
"""The behavior of 3.2 and lower. Deprecated and will be changed in 3.3."""
process_multipart(entity)
params = entity.params
for part in entity.parts:
if part.name is None:
key = ntou('parts')
else:
key = part.name
if part.filename is None:
# It's a regular field
value = part.fullvalue()
else:
# It's a file upload. Retain the whole part so consumer code
# has access to its .file and .filename attributes.
value = part
if key in params:
if not isinstance(params[key], list):
params[key] = [params[key]]
params[key].append(value)
else:
params[key] = value
# --------------------------------- Entities --------------------------------- #
class Entity(object):
"""An HTTP request body, or MIME multipart body.
This class collects information about the HTTP request entity. When a
given entity is of MIME type "multipart", each part is parsed into its own
Entity instance, and the set of parts stored in
:attr:`entity.parts<cherrypy._cpreqbody.Entity.parts>`.
Between the ``before_request_body`` and ``before_handler`` tools, CherryPy
tries to process the request body (if any) by calling
:func:`request.body.process<cherrypy._cpreqbody.RequestBody.process`.
This uses the ``content_type`` of the Entity to look up a suitable processor
in :attr:`Entity.processors<cherrypy._cpreqbody.Entity.processors>`, a dict.
If a matching processor cannot be found for the complete Content-Type,
it tries again using the major type. For example, if a request with an
entity of type "image/jpeg" arrives, but no processor can be found for
that complete type, then one is sought for the major type "image". If a
processor is still not found, then the
:func:`default_proc<cherrypy._cpreqbody.Entity.default_proc>` method of the
Entity is called (which does nothing by default; you can override this too).
CherryPy includes processors for the "application/x-www-form-urlencoded"
type, the "multipart/form-data" type, and the "multipart" major type.
CherryPy 3.2 processes these types almost exactly as older versions.
Parts are passed as arguments to the page handler using their
``Content-Disposition.name`` if given, otherwise in a generic "parts"
argument. Each such part is either a string, or the
:class:`Part<cherrypy._cpreqbody.Part>` itself if it's a file. (In this
case it will have ``file`` and ``filename`` attributes, or possibly a
``value`` attribute). Each Part is itself a subclass of
Entity, and has its own ``process`` method and ``processors`` dict.
There is a separate processor for the "multipart" major type which is more
flexible, and simply stores all multipart parts in
:attr:`request.body.parts<cherrypy._cpreqbody.Entity.parts>`. You can
enable it with::
cherrypy.request.body.processors['multipart'] = _cpreqbody.process_multipart
in an ``on_start_resource`` tool.
"""
# http://tools.ietf.org/html/rfc2046#section-4.1.2:
# "The default character set, which must be assumed in the
# absence of a charset parameter, is US-ASCII."
# However, many browsers send data in utf-8 with no charset.
attempt_charsets = ['utf-8']
"""A list of strings, each of which should be a known encoding.
When the Content-Type of the request body warrants it, each of the given
encodings will be tried in order. The first one to successfully decode the
entity without raising an error is stored as
:attr:`entity.charset<cherrypy._cpreqbody.Entity.charset>`. This defaults
to ``['utf-8']`` (plus 'ISO-8859-1' for "text/\*" types, as required by
`HTTP/1.1 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1>`_),
but ``['us-ascii', 'utf-8']`` for multipart parts.
"""
charset = None
"""The successful decoding; see "attempt_charsets" above."""
content_type = None
"""The value of the Content-Type request header.
If the Entity is part of a multipart payload, this will be the Content-Type
given in the MIME headers for this part.
"""
default_content_type = 'application/x-www-form-urlencoded'
"""This defines a default ``Content-Type`` to use if no Content-Type header
is given. The empty string is used for RequestBody, which results in the
request body not being read or parsed at all. This is by design; a missing
``Content-Type`` header in the HTTP request entity is an error at best,
and a security hole at worst. For multipart parts, however, the MIME spec
declares that a part with no Content-Type defaults to "text/plain"
(see :class:`Part<cherrypy._cpreqbody.Part>`).
"""
filename = None
"""The ``Content-Disposition.filename`` header, if available."""
fp = None
"""The readable socket file object."""
headers = None
"""A dict of request/multipart header names and values.
This is a copy of the ``request.headers`` for the ``request.body``;
for multipart parts, it is the set of headers for that part.
"""
length = None
"""The value of the ``Content-Length`` header, if provided."""
name = None
"""The "name" parameter of the ``Content-Disposition`` header, if any."""
params = None
"""
If the request Content-Type is 'application/x-www-form-urlencoded' or
multipart, this will be a dict of the params pulled from the entity
body; that is, it will be the portion of request.params that come
from the message body (sometimes called "POST params", although they
can be sent with various HTTP method verbs). This value is set between
the 'before_request_body' and 'before_handler' hooks (assuming that
process_request_body is True)."""
processors = {'application/x-www-form-urlencoded': process_urlencoded,
'multipart/form-data': process_multipart_form_data,
'multipart': process_multipart,
}
"""A dict of Content-Type names to processor methods."""
parts = None
"""A list of Part instances if ``Content-Type`` is of major type "multipart"."""
part_class = None
"""The class used for multipart parts.
You can replace this with custom subclasses to alter the processing of
multipart parts.
"""
def __init__(self, fp, headers, params=None, parts=None):
# Make an instance-specific copy of the class processors
# so Tools, etc. can replace them per-request.
self.processors = self.processors.copy()
self.fp = fp
self.headers = headers
if params is None:
params = {}
self.params = params
if parts is None:
parts = []
self.parts = parts
# Content-Type
self.content_type = headers.elements('Content-Type')
if self.content_type:
self.content_type = self.content_type[0]
else:
self.content_type = httputil.HeaderElement.from_str(
self.default_content_type)
# Copy the class 'attempt_charsets', prepending any Content-Type charset
dec = self.content_type.params.get("charset", None)
if dec:
self.attempt_charsets = [dec] + [c for c in self.attempt_charsets
if c != dec]
else:
self.attempt_charsets = self.attempt_charsets[:]
# Length
self.length = None
clen = headers.get('Content-Length', None)
# If Transfer-Encoding is 'chunked', ignore any Content-Length.
if clen is not None and 'chunked' not in headers.get('Transfer-Encoding', ''):
try:
self.length = int(clen)
except ValueError:
pass
# Content-Disposition
self.name = None
self.filename = None
disp = headers.elements('Content-Disposition')
if disp:
disp = disp[0]
if 'name' in disp.params:
self.name = disp.params['name']
if self.name.startswith('"') and self.name.endswith('"'):
self.name = self.name[1:-1]
if 'filename' in disp.params:
self.filename = disp.params['filename']
if self.filename.startswith('"') and self.filename.endswith('"'):
self.filename = self.filename[1:-1]
# The 'type' attribute is deprecated in 3.2; remove it in 3.3.
type = property(lambda self: self.content_type,
doc="""A deprecated alias for :attr:`content_type<cherrypy._cpreqbody.Entity.content_type>`.""")
def read(self, size=None, fp_out=None):
return self.fp.read(size, fp_out)
def readline(self, size=None):
return self.fp.readline(size)
def readlines(self, sizehint=None):
return self.fp.readlines(sizehint)
def __iter__(self):
return self
def __next__(self):
line = self.readline()
if not line:
raise StopIteration
return line
def next(self):
return self.__next__()
def read_into_file(self, fp_out=None):
"""Read the request body into fp_out (or make_file() if None). Return fp_out."""
if fp_out is None:
fp_out = self.make_file()
self.read(fp_out=fp_out)
return fp_out
def make_file(self):
"""Return a file-like object into which the request body will be read.
By default, this will return a TemporaryFile. Override as needed.
See also :attr:`cherrypy._cpreqbody.Part.maxrambytes`."""
return tempfile.TemporaryFile()
def fullvalue(self):
"""Return this entity as a string, whether stored in a file or not."""
if self.file:
# It was stored in a tempfile. Read it.
self.file.seek(0)
value = self.file.read()
self.file.seek(0)
else:
value = self.value
return value
def process(self):
"""Execute the best-match processor for the given media type."""
proc = None
ct = self.content_type.value
try:
proc = self.processors[ct]
except KeyError:
toptype = ct.split('/', 1)[0]
try:
proc = self.processors[toptype]
except KeyError:
pass
if proc is None:
self.default_proc()
else:
proc(self)
def default_proc(self):
"""Called if a more-specific processor is not found for the ``Content-Type``."""
# Leave the fp alone for someone else to read. This works fine
# for request.body, but the Part subclasses need to override this
# so they can move on to the next part.
pass
class Part(Entity):
"""A MIME part entity, part of a multipart entity."""
# "The default character set, which must be assumed in the absence of a
# charset parameter, is US-ASCII."
attempt_charsets = ['us-ascii', 'utf-8']
"""A list of strings, each of which should be a known encoding.
When the Content-Type of the request body warrants it, each of the given
encodings will be tried in order. The first one to successfully decode the
entity without raising an error is stored as
:attr:`entity.charset<cherrypy._cpreqbody.Entity.charset>`. This defaults
to ``['utf-8']`` (plus 'ISO-8859-1' for "text/\*" types, as required by
`HTTP/1.1 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1>`_),
but ``['us-ascii', 'utf-8']`` for multipart parts.
"""
boundary = None
"""The MIME multipart boundary."""
default_content_type = 'text/plain'
"""This defines a default ``Content-Type`` to use if no Content-Type header
is given. The empty string is used for RequestBody, which results in the
request body not being read or parsed at all. This is by design; a missing
``Content-Type`` header in the HTTP request entity is an error at best,
and a security hole at worst. For multipart parts, however (this class),
the MIME spec declares that a part with no Content-Type defaults to
"text/plain".
"""
# This is the default in stdlib cgi. We may want to increase it.
maxrambytes = 1000
"""The threshold of bytes after which point the ``Part`` will store its data
in a file (generated by :func:`make_file<cherrypy._cprequest.Entity.make_file>`)
instead of a string. Defaults to 1000, just like the :mod:`cgi` module in
Python's standard library.
"""
def __init__(self, fp, headers, boundary):
Entity.__init__(self, fp, headers)
self.boundary = boundary
self.file = None
self.value = None
def from_fp(cls, fp, boundary):
headers = cls.read_headers(fp)
return cls(fp, headers, boundary)
from_fp = classmethod(from_fp)
def read_headers(cls, fp):
headers = httputil.HeaderMap()
while True:
line = fp.readline()
if not line:
# No more data--illegal end of headers
raise EOFError("Illegal end of headers.")
if line == ntob('\r\n'):
# Normal end of headers
break
if not line.endswith(ntob('\r\n')):
raise ValueError("MIME requires CRLF terminators: %r" % line)
if line[0] in ntob(' \t'):
# It's a continuation line.
v = line.strip().decode('ISO-8859-1')
else:
k, v = line.split(ntob(":"), 1)
k = k.strip().decode('ISO-8859-1')
v = v.strip().decode('ISO-8859-1')
existing = headers.get(k)
if existing:
v = ", ".join((existing, v))
headers[k] = v
return headers
read_headers = classmethod(read_headers)
def read_lines_to_boundary(self, fp_out=None):
"""Read bytes from self.fp and return or write them to a file.
If the 'fp_out' argument is None (the default), all bytes read are
returned in a single byte string.
If the 'fp_out' argument is not None, it must be a file-like object that
supports the 'write' method; all bytes read will be written to the fp,
and that fp is returned.
"""
endmarker = self.boundary + ntob("--")
delim = ntob("")
prev_lf = True
lines = []
seen = 0
while True:
line = self.fp.readline(1<<16)
if not line:
raise EOFError("Illegal end of multipart body.")
if line.startswith(ntob("--")) and prev_lf:
strippedline = line.strip()
if strippedline == self.boundary:
break
if strippedline == endmarker:
self.fp.finish()
break
line = delim + line
if line.endswith(ntob("\r\n")):
delim = ntob("\r\n")
line = line[:-2]
prev_lf = True
elif line.endswith(ntob("\n")):
delim = ntob("\n")
line = line[:-1]
prev_lf = True
else:
delim = ntob("")
prev_lf = False
if fp_out is None:
lines.append(line)
seen += len(line)
if seen > self.maxrambytes:
fp_out = self.make_file()
for line in lines:
fp_out.write(line)
else:
fp_out.write(line)
if fp_out is None:
result = ntob('').join(lines)
for charset in self.attempt_charsets:
try:
result = result.decode(charset)
except UnicodeDecodeError:
pass
else:
self.charset = charset
return result
else:
raise cherrypy.HTTPError(
400, "The request entity could not be decoded. The following "
"charsets were attempted: %s" % repr(self.attempt_charsets))
else:
fp_out.seek(0)
return fp_out
def default_proc(self):
"""Called if a more-specific processor is not found for the ``Content-Type``."""
if self.filename:
# Always read into a file if a .filename was given.
self.file = self.read_into_file()
else:
result = self.read_lines_to_boundary()
if isinstance(result, basestring):
self.value = result
else:
self.file = result
def read_into_file(self, fp_out=None):
"""Read the request body into fp_out (or make_file() if None). Return fp_out."""
if fp_out is None:
fp_out = self.make_file()
self.read_lines_to_boundary(fp_out=fp_out)
return fp_out
Entity.part_class = Part
try:
inf = float('inf')
except ValueError:
# Python 2.4 and lower
class Infinity(object):
def __cmp__(self, other):
return 1
def __sub__(self, other):
return self
inf = Infinity()
comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding',
'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control', 'Connection',
'Content-Encoding', 'Content-Language', 'Expect', 'If-Match',
'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'Te', 'Trailer',
'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning', 'Www-Authenticate']
class SizedReader:
def __init__(self, fp, length, maxbytes, bufsize=DEFAULT_BUFFER_SIZE, has_trailers=False):
# Wrap our fp in a buffer so peek() works
self.fp = fp
self.length = length
self.maxbytes = maxbytes
self.buffer = ntob('')
self.bufsize = bufsize
self.bytes_read = 0
self.done = False
self.has_trailers = has_trailers
def read(self, size=None, fp_out=None):
"""Read bytes from the request body and return or write them to a file.
A number of bytes less than or equal to the 'size' argument are read
off the socket. The actual number of bytes read are tracked in
self.bytes_read. The number may be smaller than 'size' when 1) the
client sends fewer bytes, 2) the 'Content-Length' request header
specifies fewer bytes than requested, or 3) the number of bytes read
exceeds self.maxbytes (in which case, 413 is raised).
If the 'fp_out' argument is None (the default), all bytes read are
returned in a single byte string.
If the 'fp_out' argument is not None, it must be a file-like object that
supports the 'write' method; all bytes read will be written to the fp,
and None is returned.
"""
if self.length is None:
if size is None:
remaining = inf
else:
remaining = size
else:
remaining = self.length - self.bytes_read
if size and size < remaining:
remaining = size
if remaining == 0:
self.finish()
if fp_out is None:
return ntob('')
else:
return None
chunks = []
# Read bytes from the buffer.
if self.buffer:
if remaining is inf:
data = self.buffer
self.buffer = ntob('')
else:
data = self.buffer[:remaining]
self.buffer = self.buffer[remaining:]
datalen = len(data)
remaining -= datalen
# Check lengths.
self.bytes_read += datalen
if self.maxbytes and self.bytes_read > self.maxbytes:
raise cherrypy.HTTPError(413)
# Store the data.
if fp_out is None:
chunks.append(data)
else:
fp_out.write(data)
# Read bytes from the socket.
while remaining > 0:
chunksize = min(remaining, self.bufsize)
try:
data = self.fp.read(chunksize)
except Exception:
e = sys.exc_info()[1]
if e.__class__.__name__ == 'MaxSizeExceeded':
# Post data is too big
raise cherrypy.HTTPError(
413, "Maximum request length: %r" % e.args[1])
else:
raise
if not data:
self.finish()
break
datalen = len(data)
remaining -= datalen
# Check lengths.
self.bytes_read += datalen
if self.maxbytes and self.bytes_read > self.maxbytes:
raise cherrypy.HTTPError(413)
# Store the data.
if fp_out is None:
chunks.append(data)
else:
fp_out.write(data)
if fp_out is None:
return ntob('').join(chunks)
def readline(self, size=None):
"""Read a line from the request body and return it."""
chunks = []
while size is None or size > 0:
chunksize = self.bufsize
if size is not None and size < self.bufsize:
chunksize = size
data = self.read(chunksize)
if not data:
break
pos = data.find(ntob('\n')) + 1
if pos:
chunks.append(data[:pos])
remainder = data[pos:]
self.buffer += remainder
self.bytes_read -= len(remainder)
break
else:
chunks.append(data)
return ntob('').join(chunks)
def readlines(self, sizehint=None):
"""Read lines from the request body and return them."""
if self.length is not None:
if sizehint is None:
sizehint = self.length - self.bytes_read
else:
sizehint = min(sizehint, self.length - self.bytes_read)
lines = []
seen = 0
while True:
line = self.readline()
if not line:
break
lines.append(line)
seen += len(line)
if seen >= sizehint:
break
return lines
def finish(self):
self.done = True
if self.has_trailers and hasattr(self.fp, 'read_trailer_lines'):
self.trailers = {}
try:
for line in self.fp.read_trailer_lines():
if line[0] in ntob(' \t'):
# It's a continuation line.
v = line.strip()
else:
try:
k, v = line.split(ntob(":"), 1)
except ValueError:
raise ValueError("Illegal header line.")
k = k.strip().title()
v = v.strip()
if k in comma_separated_headers:
existing = self.trailers.get(envname)
if existing:
v = ntob(", ").join((existing, v))
self.trailers[k] = v
except Exception:
e = sys.exc_info()[1]
if e.__class__.__name__ == 'MaxSizeExceeded':
# Post data is too big
raise cherrypy.HTTPError(
413, "Maximum request length: %r" % e.args[1])
else:
raise
class RequestBody(Entity):
"""The entity of the HTTP request."""
bufsize = 8 * 1024
"""The buffer size used when reading the socket."""
# Don't parse the request body at all if the client didn't provide
# a Content-Type header. See http://www.cherrypy.org/ticket/790
default_content_type = ''
"""This defines a default ``Content-Type`` to use if no Content-Type header
is given. The empty string is used for RequestBody, which results in the
request body not being read or parsed at all. This is by design; a missing
``Content-Type`` header in the HTTP request entity is an error at best,
and a security hole at worst. For multipart parts, however, the MIME spec
declares that a part with no Content-Type defaults to "text/plain"
(see :class:`Part<cherrypy._cpreqbody.Part>`).
"""
maxbytes = None
"""Raise ``MaxSizeExceeded`` if more bytes than this are read from the socket."""
def __init__(self, fp, headers, params=None, request_params=None):
Entity.__init__(self, fp, headers, params)
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
# When no explicit charset parameter is provided by the
# sender, media subtypes of the "text" type are defined
# to have a default charset value of "ISO-8859-1" when
# received via HTTP.
if self.content_type.value.startswith('text/'):
for c in ('ISO-8859-1', 'iso-8859-1', 'Latin-1', 'latin-1'):
if c in self.attempt_charsets:
break
else:
self.attempt_charsets.append('ISO-8859-1')
# Temporary fix while deprecating passing .parts as .params.
self.processors['multipart'] = _old_process_multipart
if request_params is None:
request_params = {}
self.request_params = request_params
def process(self):
"""Process the request entity based on its Content-Type."""
# "The presence of a message-body in a request is signaled by the
# inclusion of a Content-Length or Transfer-Encoding header field in
# the request's message-headers."
# It is possible to send a POST request with no body, for example;
# however, app developers are responsible in that case to set
# cherrypy.request.process_body to False so this method isn't called.
h = cherrypy.serving.request.headers
if 'Content-Length' not in h and 'Transfer-Encoding' not in h:
raise cherrypy.HTTPError(411)
self.fp = SizedReader(self.fp, self.length,
self.maxbytes, bufsize=self.bufsize,
has_trailers='Trailer' in h)
super(RequestBody, self).process()
# Body params should also be a part of the request_params
# add them in here.
request_params = self.request_params
for key, value in self.params.items():
# Python 2 only: keyword arguments must be byte strings (type 'str').
if sys.version_info < (3, 0):
if isinstance(key, unicode):
key = key.encode('ISO-8859-1')
if key in request_params:
if not isinstance(request_params[key], list):
request_params[key] = [request_params[key]]
request_params[key].append(value)
else:
request_params[key] = value

View File

@ -1,956 +0,0 @@
import os
import sys
import time
import warnings
import cherrypy
from cherrypy._cpcompat import basestring, copykeys, ntob, unicodestr
from cherrypy._cpcompat import SimpleCookie, CookieError, py3k
from cherrypy import _cpreqbody, _cpconfig
from cherrypy._cperror import format_exc, bare_error
from cherrypy.lib import httputil, file_generator
class Hook(object):
"""A callback and its metadata: failsafe, priority, and kwargs."""
callback = None
"""
The bare callable that this Hook object is wrapping, which will
be called when the Hook is called."""
failsafe = False
"""
If True, the callback is guaranteed to run even if other callbacks
from the same call point raise exceptions."""
priority = 50
"""
Defines the order of execution for a list of Hooks. Priority numbers
should be limited to the closed interval [0, 100], but values outside
this range are acceptable, as are fractional values."""
kwargs = {}
"""
A set of keyword arguments that will be passed to the
callable on each call."""
def __init__(self, callback, failsafe=None, priority=None, **kwargs):
self.callback = callback
if failsafe is None:
failsafe = getattr(callback, "failsafe", False)
self.failsafe = failsafe
if priority is None:
priority = getattr(callback, "priority", 50)
self.priority = priority
self.kwargs = kwargs
def __lt__(self, other):
# Python 3
return self.priority < other.priority
def __cmp__(self, other):
# Python 2
return cmp(self.priority, other.priority)
def __call__(self):
"""Run self.callback(**self.kwargs)."""
return self.callback(**self.kwargs)
def __repr__(self):
cls = self.__class__
return ("%s.%s(callback=%r, failsafe=%r, priority=%r, %s)"
% (cls.__module__, cls.__name__, self.callback,
self.failsafe, self.priority,
", ".join(['%s=%r' % (k, v)
for k, v in self.kwargs.items()])))
class HookMap(dict):
"""A map of call points to lists of callbacks (Hook objects)."""
def __new__(cls, points=None):
d = dict.__new__(cls)
for p in points or []:
d[p] = []
return d
def __init__(self, *a, **kw):
pass
def attach(self, point, callback, failsafe=None, priority=None, **kwargs):
"""Append a new Hook made from the supplied arguments."""
self[point].append(Hook(callback, failsafe, priority, **kwargs))
def run(self, point):
"""Execute all registered Hooks (callbacks) for the given point."""
exc = None
hooks = self[point]
hooks.sort()
for hook in hooks:
# Some hooks are guaranteed to run even if others at
# the same hookpoint fail. We will still log the failure,
# but proceed on to the next hook. The only way
# to stop all processing from one of these hooks is
# to raise SystemExit and stop the whole server.
if exc is None or hook.failsafe:
try:
hook()
except (KeyboardInterrupt, SystemExit):
raise
except (cherrypy.HTTPError, cherrypy.HTTPRedirect,
cherrypy.InternalRedirect):
exc = sys.exc_info()[1]
except:
exc = sys.exc_info()[1]
cherrypy.log(traceback=True, severity=40)
if exc:
raise exc
def __copy__(self):
newmap = self.__class__()
# We can't just use 'update' because we want copies of the
# mutable values (each is a list) as well.
for k, v in self.items():
newmap[k] = v[:]
return newmap
copy = __copy__
def __repr__(self):
cls = self.__class__
return "%s.%s(points=%r)" % (cls.__module__, cls.__name__, copykeys(self))
# Config namespace handlers
def hooks_namespace(k, v):
"""Attach bare hooks declared in config."""
# Use split again to allow multiple hooks for a single
# hookpoint per path (e.g. "hooks.before_handler.1").
# Little-known fact you only get from reading source ;)
hookpoint = k.split(".", 1)[0]
if isinstance(v, basestring):
v = cherrypy.lib.attributes(v)
if not isinstance(v, Hook):
v = Hook(v)
cherrypy.serving.request.hooks[hookpoint].append(v)
def request_namespace(k, v):
"""Attach request attributes declared in config."""
# Provides config entries to set request.body attrs (like attempt_charsets).
if k[:5] == 'body.':
setattr(cherrypy.serving.request.body, k[5:], v)
else:
setattr(cherrypy.serving.request, k, v)
def response_namespace(k, v):
"""Attach response attributes declared in config."""
# Provides config entries to set default response headers
# http://cherrypy.org/ticket/889
if k[:8] == 'headers.':
cherrypy.serving.response.headers[k.split('.', 1)[1]] = v
else:
setattr(cherrypy.serving.response, k, v)
def error_page_namespace(k, v):
"""Attach error pages declared in config."""
if k != 'default':
k = int(k)
cherrypy.serving.request.error_page[k] = v
hookpoints = ['on_start_resource', 'before_request_body',
'before_handler', 'before_finalize',
'on_end_resource', 'on_end_request',
'before_error_response', 'after_error_response']
class Request(object):
"""An HTTP request.
This object represents the metadata of an HTTP request message;
that is, it contains attributes which describe the environment
in which the request URL, headers, and body were sent (if you
want tools to interpret the headers and body, those are elsewhere,
mostly in Tools). This 'metadata' consists of socket data,
transport characteristics, and the Request-Line. This object
also contains data regarding the configuration in effect for
the given URL, and the execution plan for generating a response.
"""
prev = None
"""
The previous Request object (if any). This should be None
unless we are processing an InternalRedirect."""
# Conversation/connection attributes
local = httputil.Host("127.0.0.1", 80)
"An httputil.Host(ip, port, hostname) object for the server socket."
remote = httputil.Host("127.0.0.1", 1111)
"An httputil.Host(ip, port, hostname) object for the client socket."
scheme = "http"
"""
The protocol used between client and server. In most cases,
this will be either 'http' or 'https'."""
server_protocol = "HTTP/1.1"
"""
The HTTP version for which the HTTP server is at least
conditionally compliant."""
base = ""
"""The (scheme://host) portion of the requested URL.
In some cases (e.g. when proxying via mod_rewrite), this may contain
path segments which cherrypy.url uses when constructing url's, but
which otherwise are ignored by CherryPy. Regardless, this value
MUST NOT end in a slash."""
# Request-Line attributes
request_line = ""
"""
The complete Request-Line received from the client. This is a
single string consisting of the request method, URI, and protocol
version (joined by spaces). Any final CRLF is removed."""
method = "GET"
"""
Indicates the HTTP method to be performed on the resource identified
by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
DELETE. CherryPy allows any extension method; however, various HTTP
servers and gateways may restrict the set of allowable methods.
CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
query_string = ""
"""
The query component of the Request-URI, a string of information to be
interpreted by the resource. The query portion of a URI follows the
path component, and is separated by a '?'. For example, the URI
'http://www.cherrypy.org/wiki?a=3&b=4' has the query component,
'a=3&b=4'."""
query_string_encoding = 'utf8'
"""
The encoding expected for query string arguments after % HEX HEX decoding).
If a query string is provided that cannot be decoded with this encoding,
404 is raised (since technically it's a different URI). If you want
arbitrary encodings to not error, set this to 'Latin-1'; you can then
encode back to bytes and re-decode to whatever encoding you like later.
"""
protocol = (1, 1)
"""The HTTP protocol version corresponding to the set
of features which should be allowed in the response. If BOTH
the client's request message AND the server's level of HTTP
compliance is HTTP/1.1, this attribute will be the tuple (1, 1).
If either is 1.0, this attribute will be the tuple (1, 0).
Lower HTTP protocol versions are not explicitly supported."""
params = {}
"""
A dict which combines query string (GET) and request entity (POST)
variables. This is populated in two stages: GET params are added
before the 'on_start_resource' hook, and POST params are added
between the 'before_request_body' and 'before_handler' hooks."""
# Message attributes
header_list = []
"""
A list of the HTTP request headers as (name, value) tuples.
In general, you should use request.headers (a dict) instead."""
headers = httputil.HeaderMap()
"""
A dict-like object containing the request headers. Keys are header
names (in Title-Case format); however, you may get and set them in
a case-insensitive manner. That is, headers['Content-Type'] and
headers['content-type'] refer to the same value. Values are header
values (decoded according to :rfc:`2047` if necessary). See also:
httputil.HeaderMap, httputil.HeaderElement."""
cookie = SimpleCookie()
"""See help(Cookie)."""
rfile = None
"""
If the request included an entity (body), it will be available
as a stream in this attribute. However, the rfile will normally
be read for you between the 'before_request_body' hook and the
'before_handler' hook, and the resulting string is placed into
either request.params or the request.body attribute.
You may disable the automatic consumption of the rfile by setting
request.process_request_body to False, either in config for the desired
path, or in an 'on_start_resource' or 'before_request_body' hook.
WARNING: In almost every case, you should not attempt to read from the
rfile stream after CherryPy's automatic mechanism has read it. If you
turn off the automatic parsing of rfile, you should read exactly the
number of bytes specified in request.headers['Content-Length'].
Ignoring either of these warnings may result in a hung request thread
or in corruption of the next (pipelined) request.
"""
process_request_body = True
"""
If True, the rfile (if any) is automatically read and parsed,
and the result placed into request.params or request.body."""
methods_with_bodies = ("POST", "PUT")
"""
A sequence of HTTP methods for which CherryPy will automatically
attempt to read a body from the rfile."""
body = None
"""
If the request Content-Type is 'application/x-www-form-urlencoded'
or multipart, this will be None. Otherwise, this will be an instance
of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>` (which you
can .read()); this value is set between the 'before_request_body' and
'before_handler' hooks (assuming that process_request_body is True)."""
# Dispatch attributes
dispatch = cherrypy.dispatch.Dispatcher()
"""
The object which looks up the 'page handler' callable and collects
config for the current request based on the path_info, other
request attributes, and the application architecture. The core
calls the dispatcher as early as possible, passing it a 'path_info'
argument.
The default dispatcher discovers the page handler by matching path_info
to a hierarchical arrangement of objects, starting at request.app.root.
See help(cherrypy.dispatch) for more information."""
script_name = ""
"""
The 'mount point' of the application which is handling this request.
This attribute MUST NOT end in a slash. If the script_name refers to
the root of the URI, it MUST be an empty string (not "/").
"""
path_info = "/"
"""
The 'relative path' portion of the Request-URI. This is relative
to the script_name ('mount point') of the application which is
handling this request."""
login = None
"""
When authentication is used during the request processing this is
set to 'False' if it failed and to the 'username' value if it succeeded.
The default 'None' implies that no authentication happened."""
# Note that cherrypy.url uses "if request.app:" to determine whether
# the call is during a real HTTP request or not. So leave this None.
app = None
"""The cherrypy.Application object which is handling this request."""
handler = None
"""
The function, method, or other callable which CherryPy will call to
produce the response. The discovery of the handler and the arguments
it will receive are determined by the request.dispatch object.
By default, the handler is discovered by walking a tree of objects
starting at request.app.root, and is then passed all HTTP params
(from the query string and POST body) as keyword arguments."""
toolmaps = {}
"""
A nested dict of all Toolboxes and Tools in effect for this request,
of the form: {Toolbox.namespace: {Tool.name: config dict}}."""
config = None
"""
A flat dict of all configuration entries which apply to the
current request. These entries are collected from global config,
application config (based on request.path_info), and from handler
config (exactly how is governed by the request.dispatch object in
effect for this request; by default, handler config can be attached
anywhere in the tree between request.app.root and the final handler,
and inherits downward)."""
is_index = None
"""
This will be True if the current request is mapped to an 'index'
resource handler (also, a 'default' handler if path_info ends with
a slash). The value may be used to automatically redirect the
user-agent to a 'more canonical' URL which either adds or removes
the trailing slash. See cherrypy.tools.trailing_slash."""
hooks = HookMap(hookpoints)
"""
A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}.
Each key is a str naming the hook point, and each value is a list
of hooks which will be called at that hook point during this request.
The list of hooks is generally populated as early as possible (mostly
from Tools specified in config), but may be extended at any time.
See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools."""
error_response = cherrypy.HTTPError(500).set_response
"""
The no-arg callable which will handle unexpected, untrapped errors
during request processing. This is not used for expected exceptions
(like NotFound, HTTPError, or HTTPRedirect) which are raised in
response to expected conditions (those should be customized either
via request.error_page or by overriding HTTPError.set_response).
By default, error_response uses HTTPError(500) to return a generic
error response to the user-agent."""
error_page = {}
"""
A dict of {error code: response filename or callable} pairs.
The error code must be an int representing a given HTTP error code,
or the string 'default', which will be used if no matching entry
is found for a given numeric code.
If a filename is provided, the file should contain a Python string-
formatting template, and can expect by default to receive format
values with the mapping keys %(status)s, %(message)s, %(traceback)s,
and %(version)s. The set of format mappings can be extended by
overriding HTTPError.set_response.
If a callable is provided, it will be called by default with keyword
arguments 'status', 'message', 'traceback', and 'version', as for a
string-formatting template. The callable must return a string or iterable of
strings which will be set to response.body. It may also override headers or
perform any other processing.
If no entry is given for an error code, and no 'default' entry exists,
a default template will be used.
"""
show_tracebacks = True
"""
If True, unexpected errors encountered during request processing will
include a traceback in the response body."""
show_mismatched_params = True
"""
If True, mismatched parameters encountered during PageHandler invocation
processing will be included in the response body."""
throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect)
"""The sequence of exceptions which Request.run does not trap."""
throw_errors = False
"""
If True, Request.run will not trap any errors (except HTTPRedirect and
HTTPError, which are more properly called 'exceptions', not errors)."""
closed = False
"""True once the close method has been called, False otherwise."""
stage = None
"""
A string containing the stage reached in the request-handling process.
This is useful when debugging a live server with hung requests."""
namespaces = _cpconfig.NamespaceSet(
**{"hooks": hooks_namespace,
"request": request_namespace,
"response": response_namespace,
"error_page": error_page_namespace,
"tools": cherrypy.tools,
})
def __init__(self, local_host, remote_host, scheme="http",
server_protocol="HTTP/1.1"):
"""Populate a new Request object.
local_host should be an httputil.Host object with the server info.
remote_host should be an httputil.Host object with the client info.
scheme should be a string, either "http" or "https".
"""
self.local = local_host
self.remote = remote_host
self.scheme = scheme
self.server_protocol = server_protocol
self.closed = False
# Put a *copy* of the class error_page into self.
self.error_page = self.error_page.copy()
# Put a *copy* of the class namespaces into self.
self.namespaces = self.namespaces.copy()
self.stage = None
def close(self):
"""Run cleanup code. (Core)"""
if not self.closed:
self.closed = True
self.stage = 'on_end_request'
self.hooks.run('on_end_request')
self.stage = 'close'
def run(self, method, path, query_string, req_protocol, headers, rfile):
r"""Process the Request. (Core)
method, path, query_string, and req_protocol should be pulled directly
from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
path
This should be %XX-unquoted, but query_string should not be.
When using Python 2, they both MUST be byte strings,
not unicode strings.
When using Python 3, they both MUST be unicode strings,
not byte strings, and preferably not bytes \x00-\xFF
disguised as unicode.
headers
A list of (name, value) tuples.
rfile
A file-like object containing the HTTP request entity.
When run() is done, the returned object should have 3 attributes:
* status, e.g. "200 OK"
* header_list, a list of (name, value) tuples
* body, an iterable yielding strings
Consumer code (HTTP servers) should then access these response
attributes to build the outbound stream.
"""
response = cherrypy.serving.response
self.stage = 'run'
try:
self.error_response = cherrypy.HTTPError(500).set_response
self.method = method
path = path or "/"
self.query_string = query_string or ''
self.params = {}
# Compare request and server HTTP protocol versions, in case our
# server does not support the requested protocol. Limit our output
# to min(req, server). We want the following output:
# request server actual written supported response
# protocol protocol response protocol feature set
# a 1.0 1.0 1.0 1.0
# b 1.0 1.1 1.1 1.0
# c 1.1 1.0 1.0 1.0
# d 1.1 1.1 1.1 1.1
# Notice that, in (b), the response will be "HTTP/1.1" even though
# the client only understands 1.0. RFC 2616 10.5.6 says we should
# only return 505 if the _major_ version is different.
rp = int(req_protocol[5]), int(req_protocol[7])
sp = int(self.server_protocol[5]), int(self.server_protocol[7])
self.protocol = min(rp, sp)
response.headers.protocol = self.protocol
# Rebuild first line of the request (e.g. "GET /path HTTP/1.0").
url = path
if query_string:
url += '?' + query_string
self.request_line = '%s %s %s' % (method, url, req_protocol)
self.header_list = list(headers)
self.headers = httputil.HeaderMap()
self.rfile = rfile
self.body = None
self.cookie = SimpleCookie()
self.handler = None
# path_info should be the path from the
# app root (script_name) to the handler.
self.script_name = self.app.script_name
self.path_info = pi = path[len(self.script_name):]
self.stage = 'respond'
self.respond(pi)
except self.throws:
raise
except:
if self.throw_errors:
raise
else:
# Failure in setup, error handler or finalize. Bypass them.
# Can't use handle_error because we may not have hooks yet.
cherrypy.log(traceback=True, severity=40)
if self.show_tracebacks:
body = format_exc()
else:
body = ""
r = bare_error(body)
response.output_status, response.header_list, response.body = r
if self.method == "HEAD":
# HEAD requests MUST NOT return a message-body in the response.
response.body = []
try:
cherrypy.log.access()
except:
cherrypy.log.error(traceback=True)
if response.timed_out:
raise cherrypy.TimeoutError()
return response
# Uncomment for stage debugging
# stage = property(lambda self: self._stage, lambda self, v: print(v))
def respond(self, path_info):
"""Generate a response for the resource at self.path_info. (Core)"""
response = cherrypy.serving.response
try:
try:
try:
if self.app is None:
raise cherrypy.NotFound()
# Get the 'Host' header, so we can HTTPRedirect properly.
self.stage = 'process_headers'
self.process_headers()
# Make a copy of the class hooks
self.hooks = self.__class__.hooks.copy()
self.toolmaps = {}
self.stage = 'get_resource'
self.get_resource(path_info)
self.body = _cpreqbody.RequestBody(
self.rfile, self.headers, request_params=self.params)
self.namespaces(self.config)
self.stage = 'on_start_resource'
self.hooks.run('on_start_resource')
# Parse the querystring
self.stage = 'process_query_string'
self.process_query_string()
# Process the body
if self.process_request_body:
if self.method not in self.methods_with_bodies:
self.process_request_body = False
self.stage = 'before_request_body'
self.hooks.run('before_request_body')
if self.process_request_body:
self.body.process()
# Run the handler
self.stage = 'before_handler'
self.hooks.run('before_handler')
if self.handler:
self.stage = 'handler'
response.body = self.handler()
# Finalize
self.stage = 'before_finalize'
self.hooks.run('before_finalize')
response.finalize()
except (cherrypy.HTTPRedirect, cherrypy.HTTPError):
inst = sys.exc_info()[1]
inst.set_response()
self.stage = 'before_finalize (HTTPError)'
self.hooks.run('before_finalize')
response.finalize()
finally:
self.stage = 'on_end_resource'
self.hooks.run('on_end_resource')
except self.throws:
raise
except:
if self.throw_errors:
raise
self.handle_error()
def process_query_string(self):
"""Parse the query string into Python structures. (Core)"""
try:
p = httputil.parse_query_string(
self.query_string, encoding=self.query_string_encoding)
except UnicodeDecodeError:
raise cherrypy.HTTPError(
404, "The given query string could not be processed. Query "
"strings for this resource must be encoded with %r." %
self.query_string_encoding)
# Python 2 only: keyword arguments must be byte strings (type 'str').
if not py3k:
for key, value in p.items():
if isinstance(key, unicode):
del p[key]
p[key.encode(self.query_string_encoding)] = value
self.params.update(p)
def process_headers(self):
"""Parse HTTP header data into Python structures. (Core)"""
# Process the headers into self.headers
headers = self.headers
for name, value in self.header_list:
# Call title() now (and use dict.__method__(headers))
# so title doesn't have to be called twice.
name = name.title()
value = value.strip()
# Warning: if there is more than one header entry for cookies (AFAIK,
# only Konqueror does that), only the last one will remain in headers
# (but they will be correctly stored in request.cookie).
if "=?" in value:
dict.__setitem__(headers, name, httputil.decode_TEXT(value))
else:
dict.__setitem__(headers, name, value)
# Handle cookies differently because on Konqueror, multiple
# cookies come on different lines with the same key
if name == 'Cookie':
try:
self.cookie.load(value)
except CookieError:
msg = "Illegal cookie name %s" % value.split('=')[0]
raise cherrypy.HTTPError(400, msg)
if not dict.__contains__(headers, 'Host'):
# All Internet-based HTTP/1.1 servers MUST respond with a 400
# (Bad Request) status code to any HTTP/1.1 request message
# which lacks a Host header field.
if self.protocol >= (1, 1):
msg = "HTTP/1.1 requires a 'Host' request header."
raise cherrypy.HTTPError(400, msg)
host = dict.get(headers, 'Host')
if not host:
host = self.local.name or self.local.ip
self.base = "%s://%s" % (self.scheme, host)
def get_resource(self, path):
"""Call a dispatcher (which sets self.handler and .config). (Core)"""
# First, see if there is a custom dispatch at this URI. Custom
# dispatchers can only be specified in app.config, not in _cp_config
# (since custom dispatchers may not even have an app.root).
dispatch = self.app.find_config(path, "request.dispatch", self.dispatch)
# dispatch() should set self.handler and self.config
dispatch(path)
def handle_error(self):
"""Handle the last unanticipated exception. (Core)"""
try:
self.hooks.run("before_error_response")
if self.error_response:
self.error_response()
self.hooks.run("after_error_response")
cherrypy.serving.response.finalize()
except cherrypy.HTTPRedirect:
inst = sys.exc_info()[1]
inst.set_response()
cherrypy.serving.response.finalize()
# ------------------------- Properties ------------------------- #
def _get_body_params(self):
warnings.warn(
"body_params is deprecated in CherryPy 3.2, will be removed in "
"CherryPy 3.3.",
DeprecationWarning
)
return self.body.params
body_params = property(_get_body_params,
doc= """
If the request Content-Type is 'application/x-www-form-urlencoded' or
multipart, this will be a dict of the params pulled from the entity
body; that is, it will be the portion of request.params that come
from the message body (sometimes called "POST params", although they
can be sent with various HTTP method verbs). This value is set between
the 'before_request_body' and 'before_handler' hooks (assuming that
process_request_body is True).
Deprecated in 3.2, will be removed for 3.3 in favor of
:attr:`request.body.params<cherrypy._cprequest.RequestBody.params>`.""")
class ResponseBody(object):
"""The body of the HTTP response (the response entity)."""
if py3k:
unicode_err = ("Page handlers MUST return bytes. Use tools.encode "
"if you wish to return unicode.")
def __get__(self, obj, objclass=None):
if obj is None:
# When calling on the class instead of an instance...
return self
else:
return obj._body
def __set__(self, obj, value):
# Convert the given value to an iterable object.
if py3k and isinstance(value, str):
raise ValueError(self.unicode_err)
if isinstance(value, basestring):
# strings get wrapped in a list because iterating over a single
# item list is much faster than iterating over every character
# in a long string.
if value:
value = [value]
else:
# [''] doesn't evaluate to False, so replace it with [].
value = []
elif py3k and isinstance(value, list):
# every item in a list must be bytes...
for i, item in enumerate(value):
if isinstance(item, str):
raise ValueError(self.unicode_err)
# Don't use isinstance here; io.IOBase which has an ABC takes
# 1000 times as long as, say, isinstance(value, str)
elif hasattr(value, 'read'):
value = file_generator(value)
elif value is None:
value = []
obj._body = value
class Response(object):
"""An HTTP Response, including status, headers, and body."""
status = ""
"""The HTTP Status-Code and Reason-Phrase."""
header_list = []
"""
A list of the HTTP response headers as (name, value) tuples.
In general, you should use response.headers (a dict) instead. This
attribute is generated from response.headers and is not valid until
after the finalize phase."""
headers = httputil.HeaderMap()
"""
A dict-like object containing the response headers. Keys are header
names (in Title-Case format); however, you may get and set them in
a case-insensitive manner. That is, headers['Content-Type'] and
headers['content-type'] refer to the same value. Values are header
values (decoded according to :rfc:`2047` if necessary).
.. seealso:: classes :class:`HeaderMap`, :class:`HeaderElement`
"""
cookie = SimpleCookie()
"""See help(Cookie)."""
body = ResponseBody()
"""The body (entity) of the HTTP response."""
time = None
"""The value of time.time() when created. Use in HTTP dates."""
timeout = 300
"""Seconds after which the response will be aborted."""
timed_out = False
"""
Flag to indicate the response should be aborted, because it has
exceeded its timeout."""
stream = False
"""If False, buffer the response body."""
def __init__(self):
self.status = None
self.header_list = None
self._body = []
self.time = time.time()
self.headers = httputil.HeaderMap()
# Since we know all our keys are titled strings, we can
# bypass HeaderMap.update and get a big speed boost.
dict.update(self.headers, {
"Content-Type": 'text/html',
"Server": "CherryPy/" + cherrypy.__version__,
"Date": httputil.HTTPDate(self.time),
})
self.cookie = SimpleCookie()
def collapse_body(self):
"""Collapse self.body to a single string; replace it and return it."""
if isinstance(self.body, basestring):
return self.body
newbody = []
for chunk in self.body:
if py3k and not isinstance(chunk, bytes):
raise TypeError("Chunk %s is not of type 'bytes'." % repr(chunk))
newbody.append(chunk)
newbody = ntob('').join(newbody)
self.body = newbody
return newbody
def finalize(self):
"""Transform headers (and cookies) into self.header_list. (Core)"""
try:
code, reason, _ = httputil.valid_status(self.status)
except ValueError:
raise cherrypy.HTTPError(500, sys.exc_info()[1].args[0])
headers = self.headers
self.status = "%s %s" % (code, reason)
self.output_status = ntob(str(code), 'ascii') + ntob(" ") + headers.encode(reason)
if self.stream:
# The upshot: wsgiserver will chunk the response if
# you pop Content-Length (or set it explicitly to None).
# Note that lib.static sets C-L to the file's st_size.
if dict.get(headers, 'Content-Length') is None:
dict.pop(headers, 'Content-Length', None)
elif code < 200 or code in (204, 205, 304):
# "All 1xx (informational), 204 (no content),
# and 304 (not modified) responses MUST NOT
# include a message-body."
dict.pop(headers, 'Content-Length', None)
self.body = ntob("")
else:
# Responses which are not streamed should have a Content-Length,
# but allow user code to set Content-Length if desired.
if dict.get(headers, 'Content-Length') is None:
content = self.collapse_body()
dict.__setitem__(headers, 'Content-Length', len(content))
# Transform our header dict into a list of tuples.
self.header_list = h = headers.output()
cookie = self.cookie.output()
if cookie:
for line in cookie.split("\n"):
if line.endswith("\r"):
# Python 2.4 emits cookies joined by LF but 2.5+ by CRLF.
line = line[:-1]
name, value = line.split(": ", 1)
if isinstance(name, unicodestr):
name = name.encode("ISO-8859-1")
if isinstance(value, unicodestr):
value = headers.encode(value)
h.append((name, value))
def check_timeout(self):
"""If now > self.time + self.timeout, set self.timed_out.
This purposefully sets a flag, rather than raising an error,
so that a monitor thread can interrupt the Response thread.
"""
if time.time() > self.time + self.timeout:
self.timed_out = True

View File

@ -1,205 +0,0 @@
"""Manage HTTP servers with CherryPy."""
import warnings
import cherrypy
from cherrypy.lib import attributes
from cherrypy._cpcompat import basestring, py3k
# We import * because we want to export check_port
# et al as attributes of this module.
from cherrypy.process.servers import *
class Server(ServerAdapter):
"""An adapter for an HTTP server.
You can set attributes (like socket_host and socket_port)
on *this* object (which is probably cherrypy.server), and call
quickstart. For example::
cherrypy.server.socket_port = 80
cherrypy.quickstart()
"""
socket_port = 8080
"""The TCP port on which to listen for connections."""
_socket_host = '127.0.0.1'
def _get_socket_host(self):
return self._socket_host
def _set_socket_host(self, value):
if value == '':
raise ValueError("The empty string ('') is not an allowed value. "
"Use '0.0.0.0' instead to listen on all active "
"interfaces (INADDR_ANY).")
self._socket_host = value
socket_host = property(_get_socket_host, _set_socket_host,
doc="""The hostname or IP address on which to listen for connections.
Host values may be any IPv4 or IPv6 address, or any valid hostname.
The string 'localhost' is a synonym for '127.0.0.1' (or '::1', if
your hosts file prefers IPv6). The string '0.0.0.0' is a special
IPv4 entry meaning "any active interface" (INADDR_ANY), and '::'
is the similar IN6ADDR_ANY for IPv6. The empty string or None are
not allowed.""")
socket_file = None
"""If given, the name of the UNIX socket to use instead of TCP/IP.
When this option is not None, the `socket_host` and `socket_port` options
are ignored."""
socket_queue_size = 5
"""The 'backlog' argument to socket.listen(); specifies the maximum number
of queued connections (default 5)."""
socket_timeout = 10
"""The timeout in seconds for accepted connections (default 10)."""
shutdown_timeout = 5
"""The time to wait for HTTP worker threads to clean up."""
protocol_version = 'HTTP/1.1'
"""The version string to write in the Status-Line of all HTTP responses,
for example, "HTTP/1.1" (the default). Depending on the HTTP server used,
this should also limit the supported features used in the response."""
thread_pool = 10
"""The number of worker threads to start up in the pool."""
thread_pool_max = -1
"""The maximum size of the worker-thread pool. Use -1 to indicate no limit."""
max_request_header_size = 500 * 1024
"""The maximum number of bytes allowable in the request headers. If exceeded,
the HTTP server should return "413 Request Entity Too Large"."""
max_request_body_size = 100 * 1024 * 1024
"""The maximum number of bytes allowable in the request body. If exceeded,
the HTTP server should return "413 Request Entity Too Large"."""
instance = None
"""If not None, this should be an HTTP server instance (such as
CPWSGIServer) which cherrypy.server will control. Use this when you need
more control over object instantiation than is available in the various
configuration options."""
ssl_context = None
"""When using PyOpenSSL, an instance of SSL.Context."""
ssl_certificate = None
"""The filename of the SSL certificate to use."""
ssl_certificate_chain = None
"""When using PyOpenSSL, the certificate chain to pass to
Context.load_verify_locations."""
ssl_private_key = None
"""The filename of the private key to use with SSL."""
if py3k:
ssl_module = 'builtin'
"""The name of a registered SSL adaptation module to use with the builtin
WSGI server. Builtin options are: 'builtin' (to use the SSL library built
into recent versions of Python). You may also register your
own classes in the wsgiserver.ssl_adapters dict."""
else:
ssl_module = 'pyopenssl'
"""The name of a registered SSL adaptation module to use with the builtin
WSGI server. Builtin options are 'builtin' (to use the SSL library built
into recent versions of Python) and 'pyopenssl' (to use the PyOpenSSL
project, which you must install separately). You may also register your
own classes in the wsgiserver.ssl_adapters dict."""
statistics = False
"""Turns statistics-gathering on or off for aware HTTP servers."""
nodelay = True
"""If True (the default since 3.1), sets the TCP_NODELAY socket option."""
wsgi_version = (1, 0)
"""The WSGI version tuple to use with the builtin WSGI server.
The provided options are (1, 0) [which includes support for PEP 3333,
which declares it covers WSGI version 1.0.1 but still mandates the
wsgi.version (1, 0)] and ('u', 0), an experimental unicode version.
You may create and register your own experimental versions of the WSGI
protocol by adding custom classes to the wsgiserver.wsgi_gateways dict."""
def __init__(self):
self.bus = cherrypy.engine
self.httpserver = None
self.interrupt = None
self.running = False
def httpserver_from_self(self, httpserver=None):
"""Return a (httpserver, bind_addr) pair based on self attributes."""
if httpserver is None:
httpserver = self.instance
if httpserver is None:
from cherrypy import _cpwsgi_server
httpserver = _cpwsgi_server.CPWSGIServer(self)
if isinstance(httpserver, basestring):
# Is anyone using this? Can I add an arg?
httpserver = attributes(httpserver)(self)
return httpserver, self.bind_addr
def start(self):
"""Start the HTTP server."""
if not self.httpserver:
self.httpserver, self.bind_addr = self.httpserver_from_self()
ServerAdapter.start(self)
start.priority = 75
def _get_bind_addr(self):
if self.socket_file:
return self.socket_file
if self.socket_host is None and self.socket_port is None:
return None
return (self.socket_host, self.socket_port)
def _set_bind_addr(self, value):
if value is None:
self.socket_file = None
self.socket_host = None
self.socket_port = None
elif isinstance(value, basestring):
self.socket_file = value
self.socket_host = None
self.socket_port = None
else:
try:
self.socket_host, self.socket_port = value
self.socket_file = None
except ValueError:
raise ValueError("bind_addr must be a (host, port) tuple "
"(for TCP sockets) or a string (for Unix "
"domain sockets), not %r" % value)
bind_addr = property(_get_bind_addr, _set_bind_addr,
doc='A (host, port) tuple for TCP sockets or a str for Unix domain sockets.')
def base(self):
"""Return the base (scheme://host[:port] or sock file) for this server."""
if self.socket_file:
return self.socket_file
host = self.socket_host
if host in ('0.0.0.0', '::'):
# 0.0.0.0 is INADDR_ANY and :: is IN6ADDR_ANY.
# Look up the host name, which should be the
# safest thing to spit out in a URL.
import socket
host = socket.gethostname()
port = self.socket_port
if self.ssl_certificate:
scheme = "https"
if port != 443:
host += ":%s" % port
else:
scheme = "http"
if port != 80:
host += ":%s" % port
return "%s://%s" % (scheme, host)

View File

@ -1,239 +0,0 @@
# This is a backport of Python-2.4's threading.local() implementation
"""Thread-local objects
(Note that this module provides a Python version of thread
threading.local class. Depending on the version of Python you're
using, there may be a faster one available. You should always import
the local class from threading.)
Thread-local objects support the management of thread-local data.
If you have data that you want to be local to a thread, simply create
a thread-local object and use its attributes:
>>> mydata = local()
>>> mydata.number = 42
>>> mydata.number
42
You can also access the local-object's dictionary:
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]
What's important about thread-local objects is that their data are
local to a thread. If we access the data in a different thread:
>>> log = []
>>> def f():
... items = mydata.__dict__.items()
... items.sort()
... log.append(items)
... mydata.number = 11
... log.append(mydata.number)
>>> import threading
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
>>> log
[[], 11]
we get different data. Furthermore, changes made in the other thread
don't affect data seen in this thread:
>>> mydata.number
42
Of course, values you get from a local object, including a __dict__
attribute, are for whatever thread was current at the time the
attribute was read. For that reason, you generally don't want to save
these values across threads, as they apply only to the thread they
came from.
You can create custom local objects by subclassing the local class:
>>> class MyLocal(local):
... number = 2
... initialized = False
... def __init__(self, **kw):
... if self.initialized:
... raise SystemError('__init__ called too many times')
... self.initialized = True
... self.__dict__.update(kw)
... def squared(self):
... return self.number ** 2
This can be useful to support default values, methods and
initialization. Note that if you define an __init__ method, it will be
called each time the local object is used in a separate thread. This
is necessary to initialize each thread's dictionary.
Now if we create a local object:
>>> mydata = MyLocal(color='red')
Now we have a default number:
>>> mydata.number
2
an initial color:
>>> mydata.color
'red'
>>> del mydata.color
And a method that operates on the data:
>>> mydata.squared()
4
As before, we can access the data in a separate thread:
>>> log = []
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
>>> log
[[('color', 'red'), ('initialized', True)], 11]
without affecting this thread's data:
>>> mydata.number
2
>>> mydata.color
Traceback (most recent call last):
...
AttributeError: 'MyLocal' object has no attribute 'color'
Note that subclasses can define slots, but they are not thread
local. They are shared across threads:
>>> class MyLocal(local):
... __slots__ = 'number'
>>> mydata = MyLocal()
>>> mydata.number = 42
>>> mydata.color = 'red'
So, the separate thread:
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
affects what we see:
>>> mydata.number
11
>>> del mydata
"""
# Threading import is at end
class _localbase(object):
__slots__ = '_local__key', '_local__args', '_local__lock'
def __new__(cls, *args, **kw):
self = object.__new__(cls)
key = 'thread.local.' + str(id(self))
object.__setattr__(self, '_local__key', key)
object.__setattr__(self, '_local__args', (args, kw))
object.__setattr__(self, '_local__lock', RLock())
if args or kw and (cls.__init__ is object.__init__):
raise TypeError("Initialization arguments are not supported")
# We need to create the thread dict in anticipation of
# __init__ being called, to make sure we don't call it
# again ourselves.
dict = object.__getattribute__(self, '__dict__')
currentThread().__dict__[key] = dict
return self
def _patch(self):
key = object.__getattribute__(self, '_local__key')
d = currentThread().__dict__.get(key)
if d is None:
d = {}
currentThread().__dict__[key] = d
object.__setattr__(self, '__dict__', d)
# we have a new instance dict, so call out __init__ if we have
# one
cls = type(self)
if cls.__init__ is not object.__init__:
args, kw = object.__getattribute__(self, '_local__args')
cls.__init__(self, *args, **kw)
else:
object.__setattr__(self, '__dict__', d)
class local(_localbase):
def __getattribute__(self, name):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__getattribute__(self, name)
finally:
lock.release()
def __setattr__(self, name, value):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__setattr__(self, name, value)
finally:
lock.release()
def __delattr__(self, name):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__delattr__(self, name)
finally:
lock.release()
def __del__():
threading_enumerate = enumerate
__getattribute__ = object.__getattribute__
def __del__(self):
key = __getattribute__(self, '_local__key')
try:
threads = list(threading_enumerate())
except:
# if enumerate fails, as it seems to do during
# shutdown, we'll skip cleanup under the assumption
# that there is nothing to clean up
return
for thread in threads:
try:
__dict__ = thread.__dict__
except AttributeError:
# Thread is dying, rest in peace
continue
if key in __dict__:
try:
del __dict__[key]
except KeyError:
pass # didn't have anything in this thread
return __del__
__del__ = __del__()
from threading import currentThread, enumerate, RLock

Some files were not shown because too many files have changed in this diff Show More