├── COPYING ├── ChangeLog ├── Makefile ├── README ├── README.windows ├── TODO ├── bin └── pygopherd ├── conf ├── mime.types └── pygopherd.conf ├── debian ├── changelog ├── compat ├── conffiles ├── control ├── copyright ├── dirs ├── docs ├── examples ├── init.d ├── postinst ├── postrm ├── pycompat ├── pygfarm.README.Debian ├── pygfarm.dirs └── rules ├── doc ├── book.sgml ├── manpage.sgml ├── pygopherd.8 ├── pygopherd.html ├── pygopherd.pdf ├── pygopherd.ps ├── pygopherd.sgml ├── pygopherd.txt ├── quickstart.sgml └── standards │ ├── Gopher+.txt │ ├── gophermap.txt │ └── url.txt ├── examples ├── gophermap └── talsample.html.tal ├── local.dsl ├── printlocal.dsl ├── pygfarm └── dict.pyg ├── pygopherd ├── GopherExceptions.py ├── GopherExceptionsTest.py ├── __init__.py ├── fileext.py ├── fileextTest.py ├── gopherentry.py ├── gopherentryTest.py ├── handlers │ ├── HandlerMultiplexer.py │ ├── UMN.py │ ├── ZIP.py │ ├── __init__.py │ ├── base.py │ ├── dir.py │ ├── file.py │ ├── gophermap.py │ ├── html.py │ ├── mbox.py │ ├── pyg.py │ ├── scriptexec.py │ ├── tal.py │ ├── url.py │ └── virtual.py ├── initialization.py ├── initializationTest.py ├── logger.py ├── loggerTest.py ├── pipe.py ├── pipeTest.py ├── protocols │ ├── ProtocolMultiplexer.py │ ├── ProtocolMultiplexerTest.py │ ├── __init__.py │ ├── base.py │ ├── baseTest.py │ ├── enhanced.py │ ├── gopherp.py │ ├── http.py │ ├── rfc1436.py │ ├── rfc1436Test.py │ └── wap.py ├── sighandlers.py ├── testutil.py └── version.py ├── runtests.py ├── runtestsgui.py ├── setup.py └── testdata ├── .abstract ├── README ├── pygopherd ├── pipetest.sh └── pipetestdata ├── symlinktest.zip ├── testarchive.tar ├── testarchive.tar.gz ├── testarchive.tgz ├── testdata.zip ├── testdata2.zip ├── testfile.txt ├── testfile.txt.gz ├── testfile.txt.gz.abstract └── ziptorture.zip /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2002, 2003 John Goerzen 2 | # 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16 | clean: 17 | -python2.3 setup.py clean --all 18 | -rm -f `find . -name "*~"` 19 | -rm -f `find . -name "*.pyc"` 20 | -rm -f `find . -name "*.pygc"` 21 | -rm -f `find . -name "*.class"` 22 | -rm -f `find . -name "*.bak"` 23 | -rm -f `find . -name ".cache*"` 24 | -rm -f *.log *.aux *.dvi *.out *.jtex jadetex.cfg 25 | -find . -name auth -exec rm -vf {}/password {}/username \; 26 | -rm -rf build 27 | 28 | changelog: 29 | git log -M -C --find-copies-harder --name-status > ChangeLog 30 | 31 | docs: doc/pygopherd.8 doc/pygopherd.html doc/pygopherd.ps \ 32 | doc/pygopherd.pdf doc/pygopherd.txt 33 | 34 | doc/pygopherd.8: doc/pygopherd.sgml 35 | docbook2man doc/pygopherd.sgml 36 | docbook2man doc/pygopherd.sgml 37 | -rm manpage.links manpage.refs 38 | mv pygopherd.8 doc 39 | 40 | #doc/pygopherd.html: doc/pygopherd.sgml 41 | # docbook2html -u doc/pygopherd.sgml 42 | # mv pygopherd.html doc 43 | 44 | doc/pygopherd.html: doc/pygopherd.sgml 45 | docbook-2-html -s local doc/pygopherd.sgml 46 | mv doc/pygopherd-html/pygopherd.html doc/pygopherd.html 47 | rm -r doc/pygopherd-html 48 | 49 | #doc/pygopherd.ps: doc/pygopherd.8 50 | # man -t -l doc/pygopherd.8 > doc/pygopherd.ps 51 | 52 | doc/pygopherd.ps: doc/pygopherd.sgml doc/book.sgml doc/manpage.sgml 53 | docbook-2-ps -q -O -V -O paper-size=Letter -s local=printlocal \ 54 | doc/book.sgml 55 | mv book.ps doc/pygopherd.ps 56 | 57 | doc/pygopherd.pdf: doc/pygopherd.ps 58 | ps2pdf doc/pygopherd.ps 59 | mv pygopherd.pdf doc 60 | 61 | doc/pygopherd.txt: 62 | groff -Tascii -man doc/pygopherd.8 | sed $$'s/.\b//g' > doc/pygopherd.txt 63 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | THIS REPOSITORY IS OBSOLETE. 2 | 3 | Pygopherd development continues with the Python 3 version at: 4 | 5 | 6 | 7 | OLD readme follows: 8 | 9 | README for Pygopherd 10 | =========================================================================== 11 | 12 | QUICKSTART (Debian) 13 | ------------------- 14 | 15 | Use the .deb: 16 | 17 | dpkg -i pygopherd.deb 18 | 19 | or 20 | 21 | apt-get install pygopherd 22 | 23 | QUICKSTART (non-Debian) 24 | ----------------------- 25 | 26 | 1. Download and install Python 2.2 or above from www.python.org, if not already 27 | present on your system. 28 | 29 | You can run pygopherd either in-place (as a regular user account) or 30 | as a system-wide daemon. For running in-place, do this: 31 | 32 | 1. Modify conf/pygopherd.conf: 33 | * Set usechroot = no 34 | * Comment out (add a # sign to the start of the line) the 35 | pidfile, setuid, and setgid lines 36 | * Set mimetypes = ./conf/mime.types 37 | * Set root = to something appropriate. 38 | * Set port to a number greater than 1024. 39 | 40 | 2. Modify the first line of executables/pygopherd to reflect 41 | the location of your Python installation. 42 | 43 | 3. Invoke pygopherd by running: 44 | ./executables/pygopherd 45 | 46 | For installing: 47 | 48 | 1. Run python2.2 setup.py install 49 | 50 | 2. Make sure that the /etc/pygopherd/pygopherd.conf names valid users 51 | (setuid, setgid) and valid document root (root). 52 | 53 | -------------------------------------------------------------------------------- /README.windows: -------------------------------------------------------------------------------- 1 | Submitted by Grant D. Watson: 2 | ---------------------------------------------------------- 3 | Windows Installation: 4 | 5 | Download the tar.gz version of the package from the website. Make sure you have Python 2.2 or above installed; if not, download and install it from http://www.python.org/. Unzip the package in "C:\Program Files" (or another suitable directory). 6 | 7 | In WordPad (_not_ Notepad) open "C:\Program Files\pygopherd\conf\pygopherd.conf". Modify the file as follows: 8 | - Set detach = no 9 | - Comment out the pidfile line 10 | - Set servertype = ThreadingTCPServer 11 | - Set usechroot = no 12 | - Comment out the setuid and setgid lines 13 | - Set root to something appropriate 14 | - Set mimetypes = conf/mime.types 15 | - Set logmethod = none 16 | 17 | To run the server, open a DOS prompt and type the following commands: 18 | c: 19 | cd "\Program Files\pygopherd" 20 | python bin\pygopherd conf\pygopherd.conf 21 | 22 | To end the server you must press Ctrl-Alt-Del and tell Windows to end the task. 23 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Document new .gophermap files 2 | -------------------------------------------------------------------------------- /bin/pygopherd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Python-based gopher server 4 | # COPYRIGHT # 5 | # Copyright (C) 2002-2019 John Goerzen 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; version 2 of the License. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # END OF COPYRIGHT # 20 | 21 | # 22 | 23 | from pygopherd import * 24 | import sys 25 | 26 | conffile = '/etc/pygopherd/pygopherd.conf' 27 | if len(sys.argv) > 1: 28 | conffile = sys.argv[1] 29 | 30 | s = initialization.initeverything(conffile) 31 | s.serve_forever() 32 | 33 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | pygopherd (2.0.18.5) unstable; urgency=medium 2 | 3 | * Correct Cvs-Git and related in debian/control. 4 | * Bump standards-version. 5 | * Build fix: use newer source:Version in control. 6 | 7 | -- John Goerzen Tue, 14 Feb 2017 19:08:39 -0600 8 | 9 | pygopherd (2.0.18.4) unstable; urgency=low 10 | 11 | * Ack NMU. Closes: #771501, #616966, #547835. 12 | * Don't crash if Numb= is empty. 13 | * Migrate to compat 9. Closes: #800204. 14 | * Correct a typo in URL handler. Closes: #663420. 15 | 16 | -- John Goerzen Sun, 29 May 2016 21:01:06 -0500 17 | 18 | pygopherd (2.0.18.3+nmu4) unstable; urgency=medium 19 | 20 | * Non-maintainer upload. 21 | * Copy gophermap from /usr/share, not /usr/share/doc (Closes: #771501) 22 | 23 | -- Cameron Norman Mon, 08 Dec 2014 17:25:38 -0800 24 | 25 | pygopherd (2.0.18.3+nmu3) unstable; urgency=low 26 | 27 | * Non-maintainer upload. 28 | * Convert to dh_python2. (Closes: #616966) 29 | 30 | -- Andrea Colangelo Fri, 28 Jun 2013 18:08:31 +0200 31 | 32 | pygopherd (2.0.18.3+nmu2) unstable; urgency=low 33 | 34 | * Non-maintainer upload. 35 | * Build-Depend on python >= 2.5.4 for install-layout=deb. 36 | 37 | -- Kumar Appaiah Mon, 19 Oct 2009 18:00:54 -0500 38 | 39 | pygopherd (2.0.18.3+nmu1) unstable; urgency=low 40 | 41 | * Non-maintainer upload. 42 | * Fix "manipulates site-packages/ directly, failing with Python 2.6" 43 | Applied and uploaded Kumar's patch, thanks dude! (Closes: #547835) 44 | 45 | -- Bastian Venthur Sat, 17 Oct 2009 15:41:30 +0200 46 | 47 | pygopherd (2.0.18.3) unstable; urgency=low 48 | 49 | * Now remove build directory in clean target. 50 | 51 | -- John Goerzen Fri, 08 Aug 2008 22:02:16 -0500 52 | 53 | pygopherd (2.0.18.2) unstable; urgency=medium 54 | 55 | * Correct typo in debian/rules. Really get RFC out of Debian source package 56 | this time. Closes: #475376. 57 | 58 | -- John Goerzen Tue, 05 Aug 2008 08:40:10 -0500 59 | 60 | pygopherd (2.0.18.1) unstable; urgency=low 61 | 62 | * Remove RFC from Debian source package. Closes: #475376. 63 | 64 | -- John Goerzen Thu, 01 May 2008 01:31:59 -0500 65 | 66 | pygopherd (2.0.18) unstable; urgency=low 67 | 68 | * Move #DEBHELPER# section to end of postinst so that adduser gets run 69 | first. Fixes a bug introduced in 2.0.17. Closes: #475247. 70 | * Removed weird stuff from source tree 71 | * Acknowledge NMU. Closes: #469619. 72 | 73 | -- John Goerzen Thu, 10 Apr 2008 00:23:56 -0500 74 | 75 | pygopherd (2.0.17-0.1) unstable; urgency=low 76 | 77 | * Non-maintainer upload to solve release goal. 78 | * Add LSB dependency header to init.d scripts (Closes: #469619). 79 | 80 | -- Petter Reinholdtsen Fri, 4 Apr 2008 20:18:22 +0200 81 | 82 | pygopherd (2.0.17) unstable; urgency=high 83 | 84 | * Make sure python-central does its thing before the init script 85 | tries to start Python. Closes: #387663. 86 | 87 | -- John Goerzen Fri, 15 Sep 2006 14:01:50 -0500 88 | 89 | pygopherd (2.0.16) unstable; urgency=medium 90 | 91 | * New config file option: timeout 92 | * Updated to latest Debian python policy (Closes: #380906) 93 | 94 | -- John Goerzen Mon, 11 Sep 2006 08:54:50 -0500 95 | 96 | pygopherd (2.0.15) unstable; urgency=low 97 | 98 | * Fixed handling of interface config option. Closes: #357315, #361435. 99 | 100 | -- John Goerzen Tue, 9 May 2006 10:43:42 -0500 101 | 102 | pygopherd (2.0.14) unstable; urgency=low 103 | 104 | * Fixed a bug where no subject line would crash the mailbox handler. 105 | * Updated ChangeLog. 106 | 107 | -- John Goerzen Thu, 22 Sep 2005 05:46:29 -0500 108 | 109 | pygopherd (2.0.13) unstable; urgency=low 110 | 111 | * Applied patch from Tim to clean up some typos in the docs. 112 | * Manpage was missing from .deb. Fixed. 113 | 114 | -- John Goerzen Sat, 19 Feb 2005 12:05:48 -0600 115 | 116 | pygopherd (2.0.12) unstable; urgency=low 117 | 118 | * Cleaner handling of connection reset by peer 119 | 120 | -- John Goerzen Sat, 15 Jan 2005 15:23:38 -0600 121 | 122 | pygopherd (2.0.11) unstable; urgency=medium 123 | 124 | * Work around a bug that occurs when a type is missing. 125 | 126 | -- John Goerzen Fri, 14 Jan 2005 09:35:25 -0600 127 | 128 | pygopherd (2.0.10) unstable; urgency=low 129 | 130 | * Some modifications to make more Windows friendly, based on patch from 131 | Grant D. Watson. 132 | 133 | -- John Goerzen Mon, 30 Aug 2004 14:49:15 -0500 134 | 135 | pygopherd (2.0.9) unstable; urgency=low 136 | 137 | * Don't crash if simpletal is unavailable. 138 | 139 | -- John Goerzen Wed, 17 Dec 2003 11:26:14 -0600 140 | 141 | pygopherd (2.0.8) unstable; urgency=low 142 | 143 | * Fixed a logic error in the Numb handling fix. 144 | 145 | -- John Goerzen Wed, 17 Dec 2003 08:43:38 -0600 146 | 147 | pygopherd (2.0.7) unstable; urgency=low 148 | 149 | * Fixed UMN.py to properly handle Numb=. 150 | 151 | -- John Goerzen Wed, 17 Dec 2003 08:25:49 -0600 152 | 153 | pygopherd (2.0.6) unstable; urgency=low 154 | 155 | * Updated debian/docs. 156 | 157 | -- John Goerzen Thu, 9 Oct 2003 16:46:37 -0500 158 | 159 | pygopherd (2.0.5) unstable; urgency=low 160 | 161 | * Added build-dep on python2.3-dev. Closes: #213938. 162 | 163 | -- John Goerzen Thu, 9 Oct 2003 11:09:41 -0500 164 | 165 | pygopherd (2.0.4) unstable; urgency=low 166 | 167 | * Eliminated outdated import of xreadlines, clearing up a warning 168 | with Python 2.3. 169 | 170 | -- John Goerzen Sun, 24 Aug 2003 17:23:41 -0500 171 | 172 | pygopherd (2.0.3) unstable; urgency=low 173 | 174 | * Fixed a silly typo in wap.py. 175 | 176 | -- John Goerzen Sat, 23 Aug 2003 20:59:12 -0500 177 | 178 | pygopherd (2.0.2) unstable; urgency=low 179 | 180 | * Can now autodetect many WAP browsers. 181 | 182 | -- John Goerzen Sat, 23 Aug 2003 17:29:20 -0500 183 | 184 | pygopherd (2.0.1) unstable; urgency=low 185 | 186 | * Oops, a stray print was left over. 187 | 188 | -- John Goerzen Sat, 23 Aug 2003 15:04:19 -0500 189 | 190 | pygopherd (2.0.0) unstable; urgency=low 191 | 192 | * New feature: WAP/WMP support! 193 | * HTTP bugfix: use \r\n line endings. 194 | * Debian: Updated for Python 2.3. 195 | * Virtual: now use | for selector sep (keep ? for legacy) 196 | for WAP compatibility. 197 | 198 | -- John Goerzen Sat, 23 Aug 2003 12:00:48 -0500 199 | 200 | pygopherd (1.99.5) unstable; urgency=low 201 | 202 | * Introduced new "inode-like" caching system for ZIP files. 203 | Now, the first time a ZIP file is accessed, it will be scanned. 204 | Links will be resolved immediately. A dictionary tree is built up; 205 | values being either file offsets in the ZIP file or dictionaries 206 | (for subdirectories). This dictionary is then cached on-disk using 207 | a binary database and marshal algorithm derived from Python's shelve 208 | module. We do not need to use pickle for this task since we are saving 209 | only simple structures. The performance gain for large ZIP files using 210 | this method is tremendous. The only time a ZIP file's central 211 | directory structure must be scanned now is the very first time it is 212 | ever accessed. 213 | 214 | -- John Goerzen Thu, 13 Mar 2003 09:49:39 -0600 215 | 216 | pygopherd (1.99.4) unstable; urgency=low 217 | 218 | * Wrote my own ZIP file reader and heavily optimized it. This reduced run 219 | times for certain torture cases from over 100 seconds to somewhere 220 | around 20, and others from over 200 seconds to around 80. 221 | 222 | -- John Goerzen Fri, 7 Mar 2003 16:03:35 -0600 223 | 224 | pygopherd (1.99.3) unstable; urgency=low 225 | 226 | * Backed out ZIP content cache. Was slower than the ZIP file itself. 227 | 228 | -- John Goerzen Thu, 6 Mar 2003 15:43:03 -0600 229 | 230 | pygopherd (1.99.2) unstable; urgency=low 231 | 232 | * Made some aggressive optimizations in the ZIP handler. 233 | 234 | -- John Goerzen Thu, 6 Mar 2003 15:34:08 -0600 235 | 236 | pygopherd (1.99.1) unstable; urgency=low 237 | 238 | * Fixed a bug in html handler. 239 | 240 | -- John Goerzen Thu, 6 Mar 2003 11:26:14 -0600 241 | 242 | pygopherd (1.99.0) unstable; urgency=low 243 | 244 | * Implemented virtual file system support. 245 | * Implemented VFS_Real, the VFS module for the on-disk filesystem 246 | (as was the only option in the past) 247 | * Implemented VFS_ZIP, the VFS module for ZIP archives. 248 | * New handler ZIP to work with VFS_ZIP. 249 | * Implemented symlink capabilities for VFS_ZIP. 250 | * Implemented directory symlink capabilities for VFS_ZIP. 251 | * Wrote extensive test battery for VFS_ZIP and VFS_ZIP's symlink handling. 252 | All tests pass. 253 | 254 | -- John Goerzen Wed, 5 Mar 2003 22:24:43 -0600 255 | 256 | pygopherd (1.1.1) unstable; urgency=low 257 | 258 | * Added ability to serve up files ending in ".gophermap" as 259 | Gophermap files. 260 | 261 | -- John Goerzen Fri, 21 Feb 2003 15:14:03 -0600 262 | 263 | pygopherd (1.1.0) unstable; urgency=low 264 | 265 | * Added new configuration option: advertisedport. 266 | * Removed TODO file (now contained in bug-tracking system) 267 | * Moved pygopherd.py to bin/pygopherd to make installation clearer 268 | and testing smoother. 269 | * Extensive new manual that thoroughly documents the system. 270 | * UMN Compatibility: type - will also hide things in the fashion of X. 271 | 272 | -- John Goerzen Thu, 08 Aug 2002 20:17:17 -0500 273 | 274 | pygopherd (1.0.0) unstable; urgency=low 275 | 276 | * First release. Welcome, pygopherd. 277 | * Added URL type rewriting handler. 278 | * Fixed gophermap info-only lines that have no tabs. 279 | * UMN dir handler now keeps directories beginning with a . out of the 280 | directory list. 281 | * Shored up tests to work with Subversion. 282 | 283 | -- John Goerzen Wed, 17 Jul 2002 19:32:32 -0500 284 | 285 | pygopherd (0.9.14) unstable; urgency=low 286 | 287 | * Fixed some more bugs -- HTMLParser. 288 | * Detect failure to allocate a socket and log it. And still scream about 289 | it. 290 | 291 | -- John Goerzen Tue, 2 Jul 2002 11:45:20 -0500 292 | 293 | pygopherd (0.9.13) unstable; urgency=low 294 | 295 | * Added signal handlers so shutdowns work properly. 296 | 297 | -- John Goerzen Tue, 2 Jul 2002 11:03:18 -0500 298 | 299 | pygopherd (0.9.12) unstable; urgency=low 300 | 301 | * Minor bugfixes. 302 | * Added support for detached operation and pidfile writing. 303 | 304 | -- John Goerzen Wed, 1 May 2002 11:46:19 -0500 305 | 306 | pygopherd (0.9.11) unstable; urgency=low 307 | 308 | * Added support for searches in HTTP handler. 309 | * Added pygfarm package and a PYG. 310 | * Many bugfixes. 311 | 312 | -- John Goerzen Thu, 18 Apr 2002 12:23:09 -0500 313 | 314 | pygopherd (0.9.10) unstable; urgency=low 315 | 316 | * Added handling of abstracts and other extended attributes. 317 | * More test cases. 318 | 319 | -- John Goerzen Mon, 15 Apr 2002 19:20:04 -0500 320 | 321 | pygopherd (0.9.4) unstable; urgency=low 322 | 323 | * Moved security handling to the handler base. 324 | * Fixed bug in URL handling -- it works now. 325 | 326 | -- John Goerzen Fri, 12 Apr 2002 11:46:14 -0500 327 | 328 | pygopherd (0.9.3) unstable; urgency=low 329 | 330 | * Fixed a bug that prevented proper startup. Fixed a lot of bugs 331 | with new HTTP code. 332 | 333 | -- John Goerzen Thu, 11 Apr 2002 16:41:02 -0500 334 | 335 | pygopherd (0.9.2) unstable; urgency=low 336 | 337 | * More bugfixes and new testing code. 338 | 339 | -- John Goerzen Thu, 11 Apr 2002 14:34:43 -0500 340 | 341 | pygopherd (0.9.1) unstable; urgency=low 342 | 343 | * Many routine bugfixes. 344 | 345 | -- John Goerzen Wed, 10 Apr 2002 19:31:13 -0500 346 | 347 | pygopherd (0.9.0) unstable; urgency=low 348 | 349 | * Initial Release. Closes: #142221. 350 | 351 | -- John Goerzen Wed, 10 Apr 2002 08:58:31 -0500 352 | 353 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/conffiles: -------------------------------------------------------------------------------- 1 | /etc/pygopherd/pygopherd.conf 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pygopherd 2 | Section: net 3 | Priority: optional 4 | Maintainer: John Goerzen 5 | Build-Depends-Indep: debhelper (>> 9), python (>= 2.6.6-3~), dh-python 6 | Build-Depends: debhelper (>= 9) 7 | Standards-Version: 3.9.8 8 | Homepage: gopher://gopher.quux.org/1/devel/gopher/pygopherd 9 | Vcs-Git: https://github.com/jgoerzen/gopher.git 10 | Vcs-Browser: https://github.com/jgoerzen/gopher.git 11 | 12 | Package: pygopherd 13 | Architecture: all 14 | Depends: python, mime-support, logrotate, python-simpletal, ${python:Depends}, ${misc:Depends}, adduser 15 | Provides: gopher-server 16 | Description: Modular Multiprotocol Gopher/HTTP/WAP Server in Python 17 | This is an all-new, modern Gopher server. It can serve documents 18 | with Gopher+, standard Gopher (RFC1436), HTTP, and WAP -- all on the same 19 | port. Pygopherd features a modular extension system as well as 20 | loadable scripts and much more. It contains full support for 21 | UMN gopherd systems -- including .Links, .names, .cap, searches, etc. 22 | Pygopherd also supports Bucktooth features such as gophermap files 23 | and executables. In addition to all this, there are Pygopherd's own 24 | extra features. All features are fully customizable and can be enabled 25 | or disabled by editing /etc/pygopherd/pygopherd.conf. 26 | . 27 | This version of PyGopherd introduces WAP support. 28 | 29 | Package: pygfarm 30 | Architecture: all 31 | Depends: pygopherd (>= ${source:Version}), python-dictclient (>= 1.0.1), ${misc:Depends} 32 | Description: Collection of add-on modules for Pygopherd 33 | These add-on modules provide additional functionality for 34 | your Pygopherd server. 35 | . 36 | Included modules are: 37 | . 38 | dict.pyg: Interface to a dictd database 39 | . 40 | These are installed into /usr/share/pygfarm for you to link to 41 | as you like. 42 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This package was debianized by John Goerzen on 2 | Wed, 10 Apr 2002 08:58:31 -0500. 3 | 4 | It was downloaded from http://quux.org/give-me-gopher/ 5 | 6 | Upstream Author(s): John Goerzen 7 | 8 | Copyright: 9 | 10 | Copyright (C) 2002-2019 John Goerzen 11 | 12 | This program is free software; you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation; version 2 of the License. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program; if not, write to the Free Software 23 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24 | 25 | Debian GNU/Linux may find the GNU General Public License, version 2, in 26 | /usr/share/common-licenses/GPL-2. 27 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | usr/bin 2 | usr/sbin 3 | usr/share/pygopherd 4 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | ChangeLog 2 | doc/pygopherd.txt 3 | doc/pygopherd.ps 4 | doc/pygopherd.pdf 5 | doc/pygopherd.html 6 | README 7 | -------------------------------------------------------------------------------- /debian/examples: -------------------------------------------------------------------------------- 1 | examples/* 2 | -------------------------------------------------------------------------------- /debian/init.d: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: pygopherd 4 | # Required-Start: $remote_fs $syslog 5 | # Required-Stop: $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | ### END INIT INFO 9 | # 10 | # skeleton example file to build /etc/init.d/ scripts. 11 | # This file should be used to construct scripts for /etc/init.d. 12 | # 13 | # Written by Miquel van Smoorenburg . 14 | # Modified for Debian GNU/Linux 15 | # by Ian Murdock . 16 | # 17 | # Version: @(#)skeleton 1.8 03-Mar-1998 miquels@cistron.nl 18 | # 19 | # This file was automatically customized by dh-make on Wed, 10 Apr 2002 08:58:31 -0500 20 | 21 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 22 | DAEMON=/usr/sbin/pygopherd 23 | NAME=pygopherd 24 | DESC="Python Gopher Server" 25 | CONF=/etc/pygopherd/pygopherd.conf 26 | 27 | test -f $DAEMON || exit 0 28 | 29 | set -e 30 | 31 | case "$1" in 32 | start) 33 | echo -n "Starting $DESC: " 34 | start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \ 35 | --exec $DAEMON -- $CONF 36 | echo "$NAME." 37 | ;; 38 | stop) 39 | echo -n "Stopping $DESC: " 40 | start-stop-daemon --oknodo --stop --quiet --pidfile /var/run/$NAME.pid 41 | echo "$NAME." 42 | ;; 43 | #reload) 44 | # 45 | # If the daemon can reload its config files on the fly 46 | # for example by sending it SIGHUP, do it here. 47 | # 48 | # If the daemon responds to changes in its config file 49 | # directly anyway, make this a do-nothing entry. 50 | # 51 | # echo "Reloading $DESC configuration files." 52 | # start-stop-daemon --stop --signal 1 --quiet --pidfile \ 53 | # /var/run/$NAME.pid --exec $DAEMON 54 | #;; 55 | restart|force-reload) 56 | # 57 | # If the "reload" option is implemented, move the "force-reload" 58 | # option to the "reload" entry above. If not, "force-reload" is 59 | # just the same as "restart". 60 | # 61 | echo -n "Restarting $DESC: " 62 | start-stop-daemon --stop --quiet --pidfile \ 63 | /var/run/$NAME.pid 64 | sleep 1 65 | start-stop-daemon --start --quiet --pidfile \ 66 | /var/run/$NAME.pid --exec $DAEMON -- $CONF 67 | echo "$NAME." 68 | ;; 69 | *) 70 | N=/etc/init.d/$NAME 71 | # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 72 | echo "Usage: $N {start|stop|restart|force-reload}" >&2 73 | exit 1 74 | ;; 75 | esac 76 | 77 | exit 0 78 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # postinst script for pygopherd 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `configure' 10 | # * `abort-upgrade' 11 | # * `abort-remove' `in-favour' 12 | # 13 | # * `abort-deconfigure' `in-favour' 14 | # `removing' 15 | # 16 | # for details, see /usr/share/doc/packaging-manual/ 17 | # 18 | # quoting from the policy: 19 | # Any necessary prompting should almost always be confined to the 20 | # post-installation script, and should be protected with a conditional 21 | # so that unnecessary prompting doesn't happen if a package's 22 | # installation fails and the `postinst' is called with `abort-upgrade', 23 | # `abort-remove' or `abort-deconfigure'. 24 | 25 | case "$1" in 26 | configure) 27 | 28 | 29 | # Allow this part to fail. 30 | 31 | set +e 32 | 33 | UNAME=gopher 34 | HOMEDIR=/var/gopher 35 | 36 | if test -d $HOMEDIR; then HOMEDIREXISTS=yes; else HOMEDIREXISTS=no; fi 37 | 38 | if ! grep -q "^${UNAME}:.*${HOMEDIR}" /etc/passwd 39 | then 40 | adduser --system --home $HOMEDIR --group $UNAME 41 | else 42 | echo "Gopher account already in place; not modifying." 43 | fi 44 | 45 | if ! grep -q "^${UNAME}:" /etc/passwd 46 | then 47 | echo Failed to create user $UNAME 48 | exit 1 49 | fi 50 | 51 | if ! grep -q \^${UNAME}: /etc/group 52 | then 53 | echo Failed to create group $UNAME 54 | exit 1 55 | fi 56 | 57 | # Restore normal error checking 58 | 59 | set -e 60 | 61 | if [ `grep ^gopher: /etc/passwd | cut -d: -f7` = '/bin/false' ] ; then 62 | chsh -s /bin/sh gopher 63 | fi 64 | 65 | 66 | if [ "$HOMEDIREXISTS" = "no" ]; then 67 | if ! test -d $HOMEDIR; then 68 | mkdir $HOMEDIR 69 | fi 70 | cp /usr/share/pygopherd/gophermap $HOMEDIR/gophermap 71 | chown -R gopher:gopher $HOMEDIR 72 | fi 73 | 74 | ;; 75 | 76 | abort-upgrade|abort-remove|abort-deconfigure) 77 | 78 | ;; 79 | 80 | *) 81 | echo "postinst called with unknown argument \`$1'" >&2 82 | exit 0 83 | ;; 84 | esac 85 | 86 | # dh_installdeb will replace this with shell code automatically 87 | # generated by other debhelper scripts. 88 | 89 | #DEBHELPER# 90 | 91 | exit 0 92 | 93 | 94 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # postrm script for pygopherd 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `remove' 10 | # * `purge' 11 | # * `upgrade' 12 | # * `failed-upgrade' 13 | # * `abort-install' 14 | # * `abort-install' 15 | # * `abort-upgrade' 16 | # * `disappear' overwrit>r> 17 | # for details, see http://www.debian.org/doc/debian-policy/ or 18 | # the debian-policy package 19 | 20 | 21 | case "$1" in 22 | purge|remove) 23 | 24 | echo "Removing old .pyc files." 25 | # Clean up .pyc and .class files left around. 26 | rm -fr /usr/lib/python2.2/site-packages/pygopherd || true 27 | 28 | ;; 29 | 30 | upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) 31 | true 32 | ;; 33 | 34 | *) 35 | echo "postrm called with unknown argument \`$1'" >&2 36 | exit 1 37 | 38 | esac 39 | 40 | 41 | # dh_installdeb will replace this with shell code automatically 42 | # generated by other debhelper scripts. 43 | 44 | #DEBHELPER# 45 | 46 | exit 0 47 | -------------------------------------------------------------------------------- /debian/pycompat: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /debian/pygfarm.README.Debian: -------------------------------------------------------------------------------- 1 | pygfarm for Debian 2 | ------------------ 3 | 4 | This package installs its files into /usr/share/pygfarm. To actually make 5 | them visible to your server, you'll need to either symlink (if not running 6 | chroot) or copy (if running chroot) them into a path under your server root. 7 | 8 | -- John Goerzen , Wed, 10 Apr 2002 08:58:31 -0500 9 | -------------------------------------------------------------------------------- /debian/pygfarm.dirs: -------------------------------------------------------------------------------- 1 | /usr/share/pygfarm 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # Sample debian/rules that uses debhelper. 3 | # GNU copyright 1997 to 1999 by Joey Hess. 4 | # Modified by John Goerzen 5 | 6 | # Uncomment this to turn on verbose mode. 7 | #export DH_VERBOSE=1 8 | 9 | PYTHON=python 10 | PACKAGE=pygopherd 11 | 12 | 13 | ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) 14 | CFLAGS += -g 15 | endif 16 | ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS))) 17 | INSTALL_PROGRAM += -s 18 | endif 19 | 20 | configure: configure-stamp 21 | configure-stamp: 22 | dh_testdir 23 | # Add here commands to configure the package. 24 | #$(PYTHON) setup.py configure 25 | 26 | touch configure-stamp 27 | 28 | 29 | build: build-stamp 30 | 31 | build-stamp: configure-stamp 32 | dh_testdir 33 | 34 | # Add here commands to compile the package. 35 | $(PYTHON) setup.py build 36 | #/usr/bin/docbook-to-man debian/pygopherd.sgml > pygopherd.1 37 | 38 | touch build-stamp 39 | 40 | clean: 41 | dh_testdir 42 | dh_testroot 43 | rm -f build-stamp configure-stamp 44 | -rm -f doc/standards/rfc* 45 | 46 | # Add here commands to clean up after the build process. 47 | -$(MAKE) clean 48 | 49 | dh_clean 50 | 51 | install: build 52 | dh_testdir 53 | dh_testroot 54 | dh_prep 55 | dh_installdirs 56 | 57 | # Add here commands to install the package into debian/pygopherd. 58 | #$(MAKE) install DESTDIR=$(CURDIR)/debian/pygopherd 59 | $(PYTHON) setup.py install --root=`pwd`/debian/$(PACKAGE) --no-compile --install-layout=deb 60 | 61 | 62 | # Build architecture-dependent files here. 63 | binary-arch: build install 64 | # We have nothing to do by default. 65 | 66 | # Build architecture-independent files here. 67 | binary-indep: build install 68 | dh_testdir 69 | dh_testroot 70 | # dh_installdebconf 71 | dh_installdocs 72 | mv debian/pygopherd/usr/bin/pygopherd \ 73 | debian/pygopherd/usr/sbin/pygopherd 74 | rm debian/pygopherd/etc/pygopherd/mime.types 75 | cp examples/gophermap debian/pygopherd/usr/share/pygopherd/gophermap 76 | cp pygfarm/*.pyg debian/pygfarm/usr/share/pygfarm 77 | chown root.root debian/pygfarm/usr/share/pygfarm/* 78 | chmod 0755 debian/pygfarm/usr/share/pygfarm/* 79 | dh_installexamples examples/* 80 | dh_installmenu 81 | # dh_installlogrotate 82 | # dh_installemacsen 83 | # dh_installpam 84 | # dh_installmime 85 | dh_installinit 86 | dh_installcron 87 | dh_installman doc/pygopherd.8 88 | dh_installinfo 89 | # dh_undocumented 90 | dh_installchangelogs 91 | dh_python2 92 | dh_link 93 | dh_strip 94 | dh_compress 95 | dh_fixperms 96 | # dh_makeshlibs 97 | dh_installdeb 98 | # dh_perl 99 | dh_shlibdeps 100 | dh_gencontrol 101 | dh_md5sums 102 | dh_builddeb 103 | 104 | binary: binary-indep binary-arch 105 | .PHONY: build clean binary-indep binary-arch binary install configure 106 | -------------------------------------------------------------------------------- /doc/book.sgml: -------------------------------------------------------------------------------- 1 | PyGopherd"> 3 | 4 | 5 | 6 | ]> 7 | 8 | 9 | PyGopherd Manual</> 10 | <pubdate>$Date$</> 11 | <edition>$Rev$</> 12 | </bookinfo> 13 | 14 | &maindoc; 15 | &manpage; 16 | </book> 17 | -------------------------------------------------------------------------------- /doc/manpage.sgml: -------------------------------------------------------------------------------- 1 | <!-- 2 | <!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [ 3 | <!ENTITY PyGopherd "<application>PyGopherd</application>"> 4 | <!ENTITY OfflineIMAP "<application>OfflineIMAP</application>"> 5 | ]> --> 6 | <!-- "file:///usr/share/sgml/docbook/dtd/xml/4.2/docbookx.dtd"> --> 7 | 8 | <reference id="pygopherd.man"> 9 | <title>PyGopherd Manpage 10 | 11 | 12 | 13 |
jgoerzen@complete.org
14 | JohnGoerzen 15 | $Date: 2003-08-25 16:14:52 -0500 (Mon, 25 Aug 2003) $ 16 |
17 | 18 | 19 | pygopherd 20 | 8 21 | John Goerzen 22 | 23 | 24 | 25 | PyGopherd 26 | Multiprotocol Information Server 27 | 28 | 29 | 30 | 31 | pygopherd 32 | configfile 33 | 34 | 35 | 36 | 37 | Description 38 | 39 | 40 | Welcome to &PyGopherd;. In a nutshell, &PyGopherd; 41 | is a modern dynamic 42 | multi-protocol hierarchical information server with a pluggable 43 | modularized extension system, 44 | full flexible caching, virtual files and 45 | folders, and autodetection of file types -- all with support for 46 | standardized yet extensible per-document metadata. Whew! Read on for 47 | information on this what all these buzzwords mean. 48 | 49 | 50 | 51 | 52 | Quick Start 53 | &quickstart; 54 | 55 | 56 | 57 | Options 58 | 59 | All &PyGopherd; configuratoin is done via the configuration 60 | file. Therefore, the program has only one command-line 61 | option: 62 | 63 | 64 | 65 | configfile 66 | This option argument specifies the location 67 | of the configuration file that &PyGopherd; is to use. 68 | 69 | 70 | 71 | 72 | 73 | 74 | Conforming To 75 | 76 | 77 | The Internet Gopher Protocol as specified in RFC1436 78 | 79 | 80 | 81 | 82 | The Gopher+ upward-compatible enhancements to the Internet Gopher 83 | Protocol from the University of Minnesota as laid out at 84 | . 86 | 87 | 88 | 89 | 90 | The gophermap file format as originally implemented in the 91 | Bucktooth gopher server and described at 92 | . 94 | 95 | 96 | 97 | 98 | The Links to URL specification as laid out by John Goerzen 99 | at 100 | . 102 | 103 | 104 | 105 | 106 | The UMN format for specifying object attributes and links 107 | with .cap, .Links, .abstract, and similar files as specified elsewhere 108 | in this document and implemented by UMN gopherd. 109 | 110 | 111 | 112 | 113 | The PYG format for extensible Python gopher objects as created for 114 | &PyGopherd;. 115 | 116 | 117 | 118 | Hypertext Transfer Protocol HTTP/1.0 as specified in 119 | RFC1945 120 | 121 | 122 | 123 | 124 | Hypertext Markup Language (HTML) 3.2 and 4.0 125 | Transitional as specified in RFC1866 and RFC2854. 126 | 127 | 128 | 129 | 130 | Maildir as specified in 131 | and 133 | . 134 | 135 | 136 | 137 | 138 | The mbox mail storage format as specified in 139 | . 141 | 142 | 143 | 144 | 145 | Registered MIME media types as specified in RFC2048. 146 | 147 | 148 | 149 | 150 | Script execution conforming to both UMN standards as laid out in UMN 151 | gopherd(1) and Bucktooth standards as specified at 152 | , 154 | so far as each can be implemented consistent with secure 155 | design principles. 156 | 157 | 158 | 159 | 160 | 161 | Standard Python 2.2.1 or above as implemented on 162 | POSIX-compliant systems. 163 | 164 | 165 | 166 | 167 | 168 | WAP/WML as defined by the WAP Forum. 169 | 170 | 171 | 172 | 173 | 174 | 175 | Bugs 176 | 177 | Reports of bugs should be sent via e-mail to the &PyGopherd; 178 | bug-tracking system (BTS) at 179 | pygopherd@bugs.complete.org or submitted online 180 | using the Web interface at . 182 | 183 | 184 | The Web site also lists all current bugs, where you can check their 185 | status or contribute to fixing them. 186 | 187 | 188 | 189 | 190 | Copyright 191 | 192 | &PyGopherd; is Copyright (C) 2002, 2003 John Goerzen. 193 | 194 | 195 | This program is free software; you can redistribute it and/or 196 | modify it under the terms of the GNU General Public License as 197 | published by the Free Software Foundation; version 2 of the 198 | License. 199 | 200 | 201 | This program is distributed in the hope that it will be useful, 202 | but WITHOUT ANY WARRANTY; without even the implied warranty of 203 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 204 | GNU General Public License for more details. 205 | 206 | 207 | You should have received a copy of the GNU General Public License 208 | along with this program; if not, write to: 209 | 210 | 211 | Free Software Foundation, Inc. 212 | 59 Temple Place 213 | Suite 330 214 | Boston, MA 02111-1307 215 | USA 216 | 217 | 218 | 219 | 220 | Author 221 | 222 | &PyGopherd;, its libraries, documentation, and all included 223 | files (except where noted) was written by John Goerzen 224 | jgoerzen@complete.org 225 | and copyright is held as stated in the 226 | Copyright section. 227 | 228 | 229 | 230 | Portions of this manual (specifically relating to certian UMN gopherd 231 | features and characteristics that PyGopherd emulates) are modified 232 | versions of the original 233 | gopherd(1) manpage accompanying the UMN gopher distribution. That 234 | document is distributed under the same terms as this, and 235 | bears the following copyright notices: 236 | 237 | 238 | Copyright (C) 1991-2000 University of Minnesota 239 | Copyright (C) 2000-2002 John Goerzen and other developers 240 | 241 | 242 | &PyGopherd; may be downloaded, and information found, from its 243 | homepage via either Gopher or HTTP: 244 | 245 | 246 | 248 | 249 | 250 | 252 | 253 | 254 | &PyGopherd; may also be downloaded using Subversion. Additionally, 255 | the distributed tar.gz may be updated with a simple "svn update" 256 | command; it is ready to go. For information on getting 257 | &PyGopherd; with Subversion, please visit . 259 | 260 | 261 | 262 | 263 | See Also 264 | 265 | python (1). 266 | 267 | 268 |
269 | -------------------------------------------------------------------------------- /doc/pygopherd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgoerzen/pygopherd/d412606bae28ce07126e8464033bfd8c04071964/doc/pygopherd.pdf -------------------------------------------------------------------------------- /doc/quickstart.sgml: -------------------------------------------------------------------------------- 1 | 2 | 3 | If you have already installed &PyGopherd; system-wide, or your 4 | administrator has done that for you, your task for setting up 5 | &PyGopherd; for the first time is quite simple. You just need 6 | to set up your configuration file, make your folder directory, 7 | and run it! 8 | 9 | 10 | 11 | You can quickly set up your configuration file. The 12 | distribution includes two files of interest: 13 | conf/pygopherd.conf and 14 | conf/mime.types. Debian users will find 15 | the configuration file pre-installed in 16 | /etc/pygopherd/pygopherd.conf and the 17 | mime.types file provided by the system 18 | already. 19 | 20 | 21 | 22 | Open up pygopherd.conf in your editor and 23 | adjust to suit. The file is heavily commented and you can 24 | refer to it for detailed information. Some settings to take a 25 | look at include: detach, 26 | pidfile, port, 27 | usechroot, setuid, 28 | setgid, and root. 29 | These may or may not work at their defaults for you. The 30 | remaining ones should be fine for a basic setup. 31 | 32 | 33 | 34 | Invoke &PyGopherd; with pygopherd 35 | path/to/configfile (or 36 | /etc/init.d/pygopherd start on Debian). 37 | Place some files in the location specified by the 38 | root directive in the config file and 39 | you're ready to run! 40 | 41 | -------------------------------------------------------------------------------- /doc/standards/gophermap.txt: -------------------------------------------------------------------------------- 1 | Serving files and the gophermap file 2 | ------------------------------------ 3 | 4 | The gophermap file is responsible for the look of a gopher menu. 5 | 6 | Unlike the UMN gopherd-style map files, which are somewhat cumbersome and 7 | can get rather large, Bucktooth encourages a slimline approach, or you can 8 | have none at all. This is not too secure since it will happily serve any and 9 | every file in its mountpoint to a greedy user, but if that's really what you 10 | want, congratulations. You can stop reading this now, since that's exactly 11 | what it will do when you install it with no gophermap files. Only gophermap, 12 | ., and .. are not served to the user. 13 | 14 | Assuming you want to do a little more customisation than that, you can 15 | edit the gophermap file (one per directory) with any text editor and follow a 16 | few simple rules to gopher goodness. (A sample file is in stuff/ for your 17 | enjoyment.) 18 | 19 | Bucktooth sends any RFC-1436 compliant line to the client. In other words, 20 | 21 | 1gopher.ptloma.edu homegopher.ptloma.edu70 22 | 23 | where , is of course, the tab (CTRL-I, 0x09) character, generates a 24 | link to "null" selector on gopher.ptloma.edu 70 with an itemtype of 1 and 25 | a display string of "gopher.ptloma.edu home". You don't even have to enter 26 | valid selectors, although this will not endear you much to your users. 27 | 28 | If you are not well-versed in RFC-1436, it breaks down to the first character 29 | being the itemtype (0 = text, 1 = gopher menu, 5 = zip file, 9 = generic 30 | binary, 7 = search server, I = generic image, g = gif image; others are 31 | also supported by some clients), then the string shown by the client up to 32 | the first tab ("display string"); then the full path to the resource 33 | ("selector"); the hostname of the server; and the port. 34 | 35 | Since this would be a drag to always have to type things out in full, 36 | Bucktooth allows the following shortcuts: 37 | 38 | * If you don't specify a port, Bucktooth provides the one your server is 39 | using (almost always 70). 40 | 41 | * If you don't specify a host, Bucktooth provides your server's hostname. 42 | 43 | * If you only specify a relative selector and not an absolute path, Bucktooth 44 | sticks on the path they're browsing. 45 | 46 | So, if your server is gopher.somenetwork.com and your server's port is 7070, 47 | and this gophermap is inside of /lotsa, then 48 | 49 | 1Lots of stuffstuff 50 | 51 | is expanded out to 52 | 53 | 1Lots of stuff/lotsa/stuffgopher.somenetwork.com7070 54 | 55 | If you don't specify a selector, two things can happen. Putting a at 56 | the end, like 57 | 58 | 1src 59 | 60 | explicitly tells Bucktooth you aren't specifying a selector, so Bucktooth 61 | uses your display string as the selector, adds on the host and port, and 62 | gives the client that. 63 | 64 | Otherwise, Bucktooth sees it as a description, and has the client display it 65 | as text. This allows you to add text descriptions to your menus. However, 66 | don't use the character anywhere in your text description or Bucktooth 67 | will try to interpret it as an RFC-1436 resource, which will yield possibly 68 | hilarious and definitely erroneous results. 69 | 70 | One last warning: keep display strings at 67 characters or less -- some 71 | clients may abnormally wrap them or display them in a way you didn't intend. 72 | 73 | 74 | . 75 | 76 | -------------------------------------------------------------------------------- /doc/standards/url.txt: -------------------------------------------------------------------------------- 1 | 2 | John Goerzen on Tue, 12 Feb 2002 14:19:47 -0500 (EST) 3 | 4 | [[1]Date Prev] [[2]Date Next] [[3]Thread Prev] [[4]Thread Next] 5 | [[5]Date Index] [[6]Thread Index] 6 | 7 | [gopher] Links to URL 8 | 9 | * To: [7]gopher AT complete SEP org 10 | * Subject: [gopher] Links to URL 11 | * From: John Goerzen <[8]jgoerzen AT complete SEP org> 12 | * Date: 12 Feb 2002 14:19:46 -0500 13 | * In-reply-to: <[9]200202120132.RAA11190@stockholm.ptloma.edu> 14 | * List-archive: <[10]http://www.complete.org/mailinglists/archives/> 15 | * List-help: <[11]mailto:listar@complete.org?Subject=help> 16 | * List-owner: <[12]mailto:jgoerzen@complete.org> 17 | * List-post: <[13]mailto:gopher@complete.org> 18 | * List-software: Listar version 1.0.0 19 | * List-subscribe: 20 | <[14]mailto:gopher-request@complete.org?Subject=subscribe> 21 | * List-unsubscribe: 22 | <[15]mailto:gopher-request@complete.org?Subject=unsubscribe> 23 | * References: <[16]200202120132.RAA11190@stockholm.ptloma.edu> 24 | * Reply-to: [17]gopher AT complete SEP org 25 | * Sender: [18]gopher-bounce AT complete SEP org 26 | * User-agent: Gnus/5.0808 (Gnus v5.8.8) XEmacs/21.4 (Common Lisp) 27 | _________________________________________________________________ 28 | 29 | 30 | I think it is best to start small with modifications to the protocol. 31 | Therefore, I propose the following: 32 | 33 | Method to link to URLs from Gopherspace 34 | --------------------------------------- 35 | 36 | 1. Protocol issues 37 | 38 | Links to URLs from a gopher directory shall be defined as follows: 39 | 40 | Type -- the appropriate character corresponding to the type of the 41 | document on the remote end; h if HTML. 42 | 43 | Path -- the full URL, preceeded by "URL:". For instance: 44 | URL:[19]http://www.complete.org/ 45 | 46 | Host, Port -- pointing back to the gopher server that provided 47 | the directory for compatibility reasons. 48 | 49 | Name -- as usual for a Gopher directory entry. 50 | 51 | 2. Conforming client requirements 52 | 53 | A client adhering to this specification will, when it sees a Gopher 54 | selector with a path starting with URL:, interpret the path as a URL. 55 | It will ignore the host and port components of the Gopher selector, 56 | using those components from the URL instead (if applicable). 57 | 58 | 3. Conforming server requirements 59 | 60 | A server with Gopher URL support will not, in most cases, need to take 61 | extra steps to provide this support beyond those outlined in 62 | Compatibility below. Servers not implementing those steps outlined in 63 | Compatibility will be deemed to be not in compliance. 64 | 65 | 4. Authoring compliance 66 | 67 | The use of URL: selectors should be avoided wherever possible. In 68 | particular, it should be avoided when pre-existing gopher facilities 69 | exist for the type of content linked. The following URL types are 70 | explicitly prohibited by this specification: 71 | 72 | gopher 73 | telnet 74 | tn3270 75 | 76 | Authors should avoid links to any document not of HTML type whenever 77 | possible. Linking to non-HTML documents will break compatibility with 78 | Gopher browsers that do not implement this specification. The ranks 79 | of these browsers include most Web browsers, so that is a significant 80 | audience. 81 | 82 | 5. Compatibility 83 | 84 | Links to HTML pages may be accomodated even for non-comforming 85 | browsers by providing additional capabilities in the server. 86 | 87 | When a non-conforming browser is instructed to follow a link to a URL, 88 | it will contact the Gopher server that provided the menu (since these 89 | are specified per section 1). 90 | 91 | When a conforming Gopher server receives a request whose path begins 92 | with URL:, it will write out a HTML document that will send the 93 | non-compliant browser to the appropriate place. One such conforming 94 | document is: 95 | 96 | 97 | 98 | 99 | 100 | 101 | You are following a link from gopher to a web site. You will be 102 | automatically taken to the web site shortly. If you do not get sent 103 | there, please click 104 | here to go to the web site. 105 |

106 | The URL linked is: 107 |

108 | [23]http://www.acm.org/classics/< 109 | /A> 110 |

111 | Thanks for using gopher! 112 | 113 | 114 | 115 | This document may be any desired by the server authors, but must 116 | adhere to these requirements: 117 | * It must provide a refresh of a duration of 10 seconds or less 118 | * It must not use IMG tags, frames, or have any reference whatsoever 119 | to content outside that particular file -- other than the link 120 | to the real destination. 121 | * It must not use JavaScript. 122 | * It must adhere to the W3C HTML 3.2 standard. 123 | 124 | When a non-conforming Gopher client finds a reference to a HTML file 125 | (type h), it will open up the file via Gopher (getting the redirect 126 | document) but using a web browser. The web browser will then be 127 | redirected to the actual link destination. Conforming clients will 128 | follow the link directly. 129 | 130 | END 131 | 132 | 133 | Comments? 134 | _________________________________________________________________ 135 | 136 | * References: 137 | + [24][gopher] Re: Gopher wishlist 138 | o From: Cameron Kaiser 139 | 140 | * Prev by Date: [25][gopher] Re: UMN Gopher on Mac OSX 141 | * Next by Date: [26][gopher] Re: Gopher wishlist 142 | * Previous by thread: [27][gopher] Re: Gopher wishlist 143 | * Next by thread: [28][gopher] Bug 144 | * Index(es): 145 | + [29]Date 146 | + [30]Thread 147 | 148 | References 149 | 150 | 1. http://www.complete.org/mailinglists/archives/gopher-200202/msg00032.html 151 | 2. http://www.complete.org/mailinglists/archives/gopher-200202/msg00034.html 152 | 3. http://www.complete.org/mailinglists/archives/gopher-200202/msg00076.html 153 | 4. http://www.complete.org/mailinglists/archives/gopher-200202/msg00021.html 154 | 5. http://www.complete.org/mailinglists/archives/gopher-200202/maillist.html#00033 155 | 6. http://www.complete.org/mailinglists/archives/gopher-200202/threads.html#00033 156 | 7. mailto:gopher@DOMAIN.HIDDEN 157 | 8. mailto:jgoerzen@DOMAIN.HIDDEN 158 | 9. http://www.complete.org/mailinglists/archives/gopher-200202/msg00026.html 159 | 10. http://www.complete.org/mailinglists/archives/ 160 | 11. mailto:listar@complete.org?Subject=help 161 | 12. mailto:jgoerzen@complete.org 162 | 13. mailto:gopher@complete.org 163 | 14. mailto:gopher-request@complete.org?Subject=subscribe 164 | 15. mailto:gopher-request@complete.org?Subject=unsubscribe 165 | 16. http://www.complete.org/mailinglists/archives/gopher-200202/msg00026.html 166 | 17. mailto:gopher@DOMAIN.HIDDEN 167 | 18. mailto:gopher-bounce@DOMAIN.HIDDEN 168 | 19. http://www.complete.org/ 169 | 20. http://www.acm.org/classics/" 170 | 21. http://www.acm.org/classics/" 171 | 22. http://www.acm.org/classics/" 172 | 23. http://www.acm.org/classics/ 173 | 24. http://www.complete.org/mailinglists/archives/gopher-200202/msg00026.html 174 | 25. http://www.complete.org/mailinglists/archives/gopher-200202/msg00032.html 175 | 26. http://www.complete.org/mailinglists/archives/gopher-200202/msg00034.html 176 | 27. http://www.complete.org/mailinglists/archives/gopher-200202/msg00076.html 177 | 28. http://www.complete.org/mailinglists/archives/gopher-200202/msg00021.html 178 | 29. http://www.complete.org/mailinglists/archives/gopher-200202/maillist.html#00033 179 | 30. http://www.complete.org/mailinglists/archives/gopher-200202/threads.html#00033 180 | -------------------------------------------------------------------------------- /examples/gophermap: -------------------------------------------------------------------------------- 1 | Welcome to Pygopherd! You can place your documents 2 | in /var/gopher for future use. You can remove the gophermap 3 | file there to get rid of this message, or you can edit it to 4 | use other things. (You'll need to do at least one of these 5 | two things in order to get your own data to show up!) 6 | 7 | Some links to get you started: 8 | 9 | 1Pygopherd Home /devel/gopher/pygopherd gopher.quux.org 70 10 | 1Quux.Org Mega Server / gopher.quux.org 70 11 | 1The Gopher Project /Software/Gopher gopher.quux.org 70 12 | 1Traditional UMN Home Gopher / gopher.tc.umn.edu 70 13 | 14 | Welcome to the world of Gopher and enjoy! 15 | -------------------------------------------------------------------------------- /examples/talsample.html.tal: -------------------------------------------------------------------------------- 1 | 2 | 3 | TAL Test 4 | 5 | 6 | My selector is: selector
7 | My MIME type is: foo/bar
8 | Another way of getting that is: foo/bar
9 | Gopher type is: X
10 | My handler is: handlername
11 | My protocol is: protocol
12 | Python path enabling status: 123
13 | My vfs is: vfs
14 | Math: 5 15 | 16 | 17 | -------------------------------------------------------------------------------- /local.dsl: -------------------------------------------------------------------------------- 1 | 3 | ]> 4 | 5 | 6 | 7 | 8 | 9 | (define (toc-depth nd) 10 | (if (string=? (gi nd) (normalize "book")) 11 | 1 12 | 1)) 13 | (define %generate-article-toc% #t) 14 | 15 | ;; Don't split up the doc as much. 16 | (define (chunk-element-list) 17 | (list (normalize "preface") 18 | (normalize "chapter") 19 | (normalize "appendix") 20 | (normalize "article") 21 | (normalize "glossary") 22 | (normalize "bibliography") 23 | (normalize "index") 24 | (normalize "colophon") 25 | (normalize "setindex") 26 | (normalize "reference") 27 | (normalize "refentry") 28 | (normalize "part") 29 | (normalize "book") ;; just in case nothing else matches... 30 | (normalize "set") ;; sets are definitely chunks... 31 | )) 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /printlocal.dsl: -------------------------------------------------------------------------------- 1 | 3 | ]> 4 | 5 | 6 | 7 | 8 | 9 | (define tex-backend #t) 10 | (define bop-footnotes #t) 11 | (define %two-side% #t) 12 | (define %footnote-ulinks% #t) 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pygopherd/GopherExceptions.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: exception declarations 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import types, re 20 | from pygopherd import logger 21 | 22 | tracebacks = 0 23 | 24 | def log(exception, protocol = None, handler = None): 25 | """Logs an exception. It will try to generate a nice-looking string 26 | based on the arguments passed in.""" 27 | protostr = 'None' 28 | handlerstr = 'None' 29 | ipaddr = 'unknown-address' 30 | exceptionclass = type(exception).__name__ 31 | if protocol: 32 | protostr = type(protocol).__name__ 33 | ipaddr = protocol.requesthandler.client_address[0] 34 | if handler: 35 | handlerstr = type(handler).__name__ 36 | 37 | logger.log("%s [%s/%s] EXCEPTION %s: %s" % \ 38 | (ipaddr, protostr, handlerstr, exceptionclass, 39 | str(exception))) 40 | 41 | def init(backtraceenabled): 42 | global tracebacks 43 | tracebacks = backtraceenabled 44 | 45 | class FileNotFound(BaseException): 46 | def __init__(self, arg): 47 | self.selector = arg 48 | self.comments = '' 49 | self.protocol = '' 50 | 51 | if type(arg) != bytes: 52 | self.selector = arg[0] 53 | self.comments = arg[1] 54 | if len(arg) > 2 and arg[2]: 55 | self.protocol = arg[2] 56 | 57 | log(self, self.protocol, None) 58 | 59 | def __str__(self): 60 | retval = "'%s' does not exist" % (self.selector) 61 | if self.comments: 62 | retval += " (%s)" % self.comments 63 | 64 | return retval 65 | -------------------------------------------------------------------------------- /pygopherd/GopherExceptionsTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Python-based gopher server 4 | # Module: test of Gopher Exceptions 5 | # COPYRIGHT # 6 | # Copyright (C) 2002 John Goerzen 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; version 2 of the License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # END OF COPYRIGHT # 21 | 22 | import unittest 23 | from io import BytesIO 24 | from pygopherd import logger, initialization, GopherExceptions, testutil 25 | from pygopherd.GopherExceptions import FileNotFound 26 | from pygopherd.protocols import rfc1436 27 | 28 | class GopherExceptionsTestCase(unittest.TestCase): 29 | def setUp(self): 30 | self.stringfile = BytesIO() 31 | self.config = testutil.getconfig() 32 | self.stringfile = testutil.getstringlogger() 33 | GopherExceptions.tracebacks = 0 34 | 35 | def testlog_basic(self): 36 | try: 37 | raise IOError("foo") 38 | except IOError as e: 39 | GopherExceptions.log(e) 40 | self.assertEqual(self.stringfile.getvalue(), 41 | "unknown-address [None/None] EXCEPTION OSError: foo\n") 42 | 43 | def testlog_proto_ip(self): 44 | rfile = BytesIO(b"/NONEXISTANT\n") 45 | wfile = BytesIO() 46 | handler = testutil.gettestinghandler(rfile, wfile, self.config) 47 | handler.handle() 48 | # handler.handle() 49 | self.assertEqual(self.stringfile.getvalue(), 50 | b"10.77.77.77 [GopherProtocol/None] EXCEPTION FileNotFound: '/NONEXISTANT' does not exist (no handler found)\n") 51 | 52 | def testFileNotFound(self): 53 | try: 54 | raise FileNotFound("TEST STRING") 55 | except FileNotFound as e: 56 | self.assertEqual(str(e), 57 | "'TEST STRING' does not exist") 58 | 59 | -------------------------------------------------------------------------------- /pygopherd/__init__.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: directory marker 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | __all__ = ['handlers', 'protocols', 'GopherExceptions', 20 | 'GopherExceptionsTest', 'gopherentry', 'gopherentryTest', 21 | 'logger', 'loggerTest', 22 | 'fileext', 'fileextTest', 'pipe', 'pipeTest', 'initialization', 23 | 'initializationTest', 'testutil', 'version'] 24 | -------------------------------------------------------------------------------- /pygopherd/fileext.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: File extension utility 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import mimetypes 20 | 21 | typemap = {} 22 | 23 | def extcmp(x, y): 24 | if x.count('.') > y.count('.'): 25 | return 1 26 | if x.count('.') < y.count('.'): 27 | return -1 28 | if len(x) > len(y): 29 | return 1 30 | if len(x) < len(y): 31 | return -1 32 | return cmp(x, y) 33 | 34 | def extstrip(file, filetype): 35 | """Strips off the extension from file given type and returns the result. 36 | Returns file unmodified if no action is possible.""" 37 | if not (filetype and filetype in typemap): 38 | return file 39 | for possible in typemap[filetype]: 40 | if file.endswith(possible): 41 | extindex = file.rfind(possible) 42 | return file[0:extindex] 43 | return file 44 | 45 | def init(): 46 | for fileext, filetype in list(mimetypes.types_map.items()): 47 | extlist = [] 48 | if filetype in typemap: 49 | extlist = typemap[filetype] 50 | 51 | baselist = [] 52 | # Add the basic extension. 53 | baselist.append(fileext) 54 | # Add it in all encoding flavors. 55 | baselist.extend( 56 | [fileext + enc for enc in list(mimetypes.encodings_map.keys())]) 57 | 58 | for shortsuff, longsuff in list(mimetypes.suffix_map.items()): 59 | if longsuff in baselist: 60 | baselist.append(shortsuff) 61 | 62 | extlist.extend(baselist) 63 | extlist.sort(extcmp) 64 | extlist.reverse() 65 | typemap[filetype] = extlist 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /pygopherd/fileextTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Python-based gopher server 4 | # Module: test of fileext 5 | # COPYRIGHT # 6 | # Copyright (C) 2002 John Goerzen 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; version 2 of the License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # END OF COPYRIGHT # 21 | 22 | import unittest 23 | from pygopherd import fileext, testutil, initialization 24 | 25 | class FileExtTestCase(unittest.TestCase): 26 | def setUp(self): 27 | config = testutil.getconfig() 28 | initialization.initmimetypes(config) 29 | 30 | def testinit(self): 31 | # Was already inited in the initmimetypes, so just do a sanity 32 | # check. 33 | self.assertTrue('.txt' in fileext.typemap['text/plain']) 34 | self.assertTrue('.txt.gz' in fileext.typemap['text/plain']) 35 | self.assertTrue(not ('.html' in fileext.typemap['text/plain'])) 36 | 37 | -------------------------------------------------------------------------------- /pygopherd/gopherentry.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: Generic gopher entry object 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import socketserver 20 | import re 21 | import os, stat, os.path, mimetypes, urllib.request, urllib.parse, urllib.error 22 | 23 | mapping = None 24 | eaexts = None 25 | 26 | class GopherEntry: 27 | """The entry object for Gopher. It holds information about each 28 | Gopher object.""" 29 | 30 | def __init__(self, selector, config): 31 | """Initialize object based on a selector and config.""" 32 | self.selector = selector # Gopher path to file 33 | self.config = config # Our config object 34 | self.fspath = None # Path to the obj in filesystem 35 | self.type = None # Gopher0 type char 36 | self.name = None # Menu name 37 | self.host = None # Hostname 38 | self.port = None # Port number (an int) 39 | self.mimetype = None # MIME type 40 | self.encodedmimetype = None # Real MIME type if encoded. 41 | self.size = None # Size 42 | self.encoding = None # Encoding type 43 | self.populated = 0 # Whether or not it's been populated 44 | self.language = None # Language 45 | self.ctime = None # Creation date 46 | self.mtime = None # Modification date 47 | self.num = 0 # Number in menu 48 | self.gopherpsupport = 0 # Supports gopher+ 49 | self.ea = {} # Extended attributes -- Gopher+ 50 | # Abstract, etc. 51 | 52 | def populatefromvfs(self, vfs, selector): 53 | self.populatefromfs(selector, statval = vfs.stat(selector), 54 | vfs = vfs) 55 | 56 | def populatefromfs(self, fspath, statval = None, vfs = None): 57 | """Fills in self with data gleaned from the filesystem. 58 | 59 | The argument fspath specifies where in the filesystem it will search. 60 | statval, if present, will be used instead of calling stat() itself 61 | so as to cut down on the number of system calls. 62 | 63 | The overall idea of this function is to set only those values that 64 | are not already set and to do so only if a definitive answer for 65 | them can be obtained from the operating system. The rest of this 66 | information describes the details of how it does this. 67 | 68 | If populatefromfs has already been called or self.populated is true, 69 | this function will note the fspath and the return without modifying 70 | anything else. 71 | 72 | If either gethost() or getport() return anything other than None, 73 | the same thing will happen. 74 | 75 | If there is no statval and os.stat returns an error, again, 76 | the same thing will happen. 77 | 78 | Assuming these tests pass, then: 79 | 80 | self.gopherpsupport will be set to true for any file or directory. 81 | 82 | The remaining values will be set only if they are not set already: 83 | 84 | For both files and directories, the creation and modification times 85 | will be noted, the name will be set to the filename (as returned 86 | by os.path.basename on the selector). 87 | 88 | For directories only, the type will be set to 1 and the mimetype to 89 | application/gopher-menu. Gopher+ protocols may wish to 90 | indicate application/gopher+-menu is available as well, but that 91 | is outside the scope of this function. 92 | 93 | For files only, the size will be noted. An attempt will be made 94 | to ascertain a mimetype and an encoding. If only a mimetype is 95 | found, it will be noted in self.mimetype. If both a mimetype 96 | and an encoding is found, self.mimetype will be 97 | application/octet-stream, self.encoding will note the encoding, 98 | and self.encodedmimetype will note the type of the encoded data. 99 | If no mimetype can be found, it will be set to the default 100 | from the config file. If no gopher0 type character is already present, 101 | self.guesstype() will be called to set it.""" 102 | 103 | self.fspath = fspath 104 | if vfs == None: 105 | from pygopherd.handlers.base import VFS_Real 106 | vfs = VFS_Real(self.config) 107 | 108 | if self.populated: 109 | return 110 | 111 | # Just let the stat catch the OSError rather than testing 112 | # for existance here. Help cut down on the number of syscalls. 113 | 114 | if not (self.gethost() == None and self.getport() == None): 115 | return 116 | 117 | if not statval: 118 | try: 119 | statval = vfs.stat(self.fspath) 120 | except OSError: 121 | return 122 | 123 | self.populated = 1 124 | self.gopherpsupport = 1 # Indicate gopher+ support for locals. 125 | 126 | # All this "or" stuff means that we only modify it if it's not already 127 | # set. 128 | 129 | self.ctime = self.ctime or statval[9] 130 | self.mtime = self.mtime or statval[8] 131 | self.name = self.name or os.path.basename(self.selector) 132 | 133 | if stat.S_ISDIR(statval[0]): 134 | self.type = self.type or '1' 135 | self.mimetype = self.mimetype or 'application/gopher-menu' 136 | self.handleeaext(self.fspath + '/', vfs) # Add the / so we get /.abs 137 | return 138 | 139 | self.handleeaext(self.fspath, vfs) 140 | 141 | self.size = self.size or statval[6] 142 | 143 | mimetype, encoding = mimetypes.guess_type(self.selector, strict = 0) 144 | 145 | if encoding: 146 | self.mimetype = self.mimetype or 'application/octet-stream' 147 | self.encoding = self.encoding or encoding 148 | self.encodedmimetype = self.encodedmimetype or mimetype 149 | else: 150 | self.mimetype = self.mimetype or mimetype 151 | 152 | # Did we get no mime type at all? Fall back to a default. 153 | 154 | if not self.mimetype: 155 | self.mimetype = self.config.get("GopherEntry", "defaultmimetype") 156 | 157 | self.type = self.type or self.guesstype() 158 | 159 | def guesstype(self): 160 | global mapping 161 | if not mapping: 162 | mapping = eval(self.config.get("GopherEntry", "mapping")) 163 | for maprule in mapping: 164 | if re.match(maprule[0], self.mimetype): 165 | return maprule[1] 166 | return '0' 167 | 168 | def handleeaext(self, selector, vfs): 169 | """Handle getting extended attributes from the filesystem.""" 170 | global eaexts 171 | if eaexts == None: 172 | eaexts = eval(self.config.get("GopherEntry", "eaexts")) 173 | if vfs == None: 174 | from pygopherd.handlers.base import VFS_Real 175 | vfs = VFS_Real(self.config) 176 | 177 | for extension, blockname in list(eaexts.items()): 178 | if blockname in self.ea: 179 | continue 180 | try: 181 | rfile = vfs.open(selector + extension, "rb") 182 | self.setea(blockname, b"\n".join( 183 | [x.rstrip() for x in rfile.readlines(20480)])) 184 | except IOError: 185 | pass 186 | 187 | 188 | def getselector(self, default = None): 189 | if self.selector == None: 190 | return default 191 | return self.selector 192 | def setselector(self, arg): 193 | self.selector = arg 194 | def getconfig(self, default = None): 195 | return self.config or default 196 | def setconfig(self, arg): 197 | self.config = arg 198 | def getfspath(self, default = None): 199 | if self.fspath == None: 200 | return default 201 | return self.fspath 202 | def setfspath(self, arg): 203 | self.fspath = arg 204 | def gettype(self, default = None): 205 | if self.type == None: 206 | return default 207 | return self.type 208 | def settype(self, arg): 209 | self.type = arg 210 | def getname(self, default = None): 211 | if self.name == None: 212 | return default 213 | return self.name 214 | def setname(self, arg): 215 | self.name = arg 216 | def gethost(self, default = None): 217 | if self.host == None: 218 | return default 219 | return self.host 220 | def sethost(self, arg): 221 | self.host = arg 222 | def getport(self, default = None): 223 | if self.port == None: 224 | return default 225 | return self.port 226 | def setport(self, arg): 227 | self.port = arg 228 | def getmimetype(self, default = None): 229 | if self.mimetype == None: 230 | return default 231 | return self.mimetype 232 | def getencodedmimetype(self, default = None): 233 | if self.encodedmimetype == None: 234 | return default 235 | return self.encodedmimetype 236 | def setencodedmimetype(self, arg): 237 | self.encodedmimetype = arg 238 | def setmimetype(self, arg): 239 | self.mimetype = arg 240 | def getsize(self, default = None): 241 | if self.size == None: 242 | return default 243 | return self.size 244 | def setsize(self, arg): 245 | self.size = arg 246 | def getencoding(self, default = None): 247 | if self.encoding == None: 248 | return default 249 | return self.encoding 250 | def setencoding(self, arg): 251 | self.encoding = arg 252 | def getlanguage(self, default = None): 253 | if self.language == None: 254 | return default 255 | return self.language 256 | def setlanguage(self, arg): 257 | self.language = arg 258 | def getctime(self, default = None): 259 | if self.ctime == None: 260 | return default 261 | return self.ctime 262 | def setctime(self, arg): 263 | self.ctime = arg 264 | def getmtime(self, default = None): 265 | if self.mtime == None: 266 | return default 267 | return self.mtime 268 | def setmtime(self, arg): 269 | self.mtime = arg 270 | def getpopulated(self, default = None): 271 | if self.populated != None: 272 | return self.populated 273 | return default 274 | def setpopulated(self, arg): 275 | self.populated = arg 276 | 277 | def geturl(self, defaulthost = 'MISSINGHOST', defaultport = 70): 278 | """If this selector is a URL: one, then we just return the rest of 279 | it. Otherwise, generate a gopher:// URL and quote it.""" 280 | if re.search("^(/|)URL:.+://", self.selector): 281 | if self.selector[0] == '/': 282 | return self.selector[5:] 283 | else: 284 | return self.selector[4:] 285 | 286 | retval = 'gopher://%s:%d/' % (self.gethost(defaulthost), 287 | self.getport(defaultport)) 288 | retval += urllib.parse.quote('%s%s' % (self.gettype(), self.getselector())) 289 | return retval 290 | 291 | def getnum(self, default = None): 292 | if self.num != None: 293 | return self.num 294 | return default 295 | def setnum(self, arg): 296 | self.num = arg 297 | 298 | def getgopherpsupport(self, default = None): 299 | if self.gopherpsupport != None: 300 | return self.gopherpsupport 301 | return default 302 | def setgopherpsupport(self, arg): 303 | self.gopherpsupport = arg 304 | 305 | def getea(self, name, default = None): 306 | if name in self.ea: 307 | return self.ea[name] 308 | return default 309 | 310 | def geteadict(self): 311 | return self.ea 312 | 313 | def setea(self, name, value): 314 | self.ea[name] = value 315 | 316 | def getinfoentry(text, config): 317 | entry = GopherEntry('fake', config) 318 | entry.name = text 319 | entry.host = '(NULL)' 320 | entry.port = 0 321 | entry.type = 'i' 322 | return entry 323 | -------------------------------------------------------------------------------- /pygopherd/gopherentryTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Python-based gopher server 4 | # Module: test of gopherentry 5 | # COPYRIGHT # 6 | # Copyright (C) 2002 John Goerzen 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; version 2 of the License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # END OF COPYRIGHT # 21 | 22 | import unittest, os, stat, re 23 | from pygopherd import testutil 24 | from pygopherd.gopherentry import GopherEntry 25 | 26 | fields = ['selector', 'config', 'fspath', 'type', 'name', 'host', 'port', 27 | 'mimetype', 'encodedmimetype', 'size', 'encoding', 28 | 'populated', 'language', 'ctime', 'mtime', 'num', 'gopherpsupport'] 29 | 30 | class GopherEntryTestCase(unittest.TestCase): 31 | def setUp(self): 32 | self.config = testutil.getconfig() 33 | self.root = self.config.get("pygopherd", "root") 34 | 35 | def assertEntryMatches(self, conditions, entry, testname): 36 | for field, value in list(conditions.items()): 37 | self.assertEqual(value, getattr(entry, field), 38 | "%s: Field '%s' expected '%s' but was '%s'" % \ 39 | (testname, field, value, getattr(entry, field))) 40 | 41 | def testinit(self): 42 | entry = GopherEntry('/NONEXISTANT', self.config) 43 | conditions = {'selector' : '/NONEXISTANT', 44 | 'config' : self.config, 45 | 'populated' : 0, 46 | 'gopherpsupport' : 0 47 | } 48 | for x in ['fspath', 'type', 'name', 'host', 'port', 'mimetype', 49 | 'encodedmimetype', 'size', 'encoding', 'language', 'ctime', 50 | 'mtime']: 51 | conditions[x] = None 52 | self.assertEntryMatches(conditions, entry, 'testinit') 53 | 54 | def testpopulate_basic(self): 55 | fspath = '/testfile.txt' 56 | statval = os.stat(self.root + fspath) 57 | conditions = {'selector' : '/testfile.txt', 58 | 'config' : self.config, 59 | 'fspath' : fspath, 60 | 'type' : '0', 61 | 'name' : 'testfile.txt', 62 | 'host' : None, 63 | 'port' : None, 64 | 'mimetype' : 'text/plain', 65 | 'encodedmimetype' : None, 66 | 'encoding' : None, 67 | 'populated' : 1, 68 | 'language' : None, 69 | 'gopherpsupport' : 1, 70 | 'ctime' : statval[9], 71 | 'mtime' : statval[8], 72 | 'size' : 5, 73 | 'num' : 0} 74 | 75 | entry = GopherEntry('/testfile.txt', self.config) 76 | entry.populatefromfs(fspath) 77 | self.assertEntryMatches(conditions, entry, 'testpopulate_basic') 78 | 79 | # Also try it with passed statval. 80 | 81 | entry = GopherEntry('/testfile.txt', self.config) 82 | entry.populatefromfs(fspath, statval) 83 | self.assertEntryMatches(conditions, entry, 84 | 'testpopulate_basic with cached stat') 85 | 86 | # Make sure it's a no-op if it's already populated. 87 | 88 | entry = GopherEntry('/NONEXISTANT', self.config) 89 | entry.populated = 1 90 | entry.populatefromfs(fspath) 91 | 92 | assert entry.gettype() == None 93 | 94 | def testpopulate_encoded(self): 95 | fspath = '/testfile.txt.gz' 96 | entry = GopherEntry('/testfile.txt.gz', self.config) 97 | entry.populatefromfs(fspath) 98 | 99 | self.assertEqual(entry.gettype(), '9') 100 | self.assertEqual(entry.getmimetype(), 'application/octet-stream') 101 | self.assertEqual(entry.getencoding(), 'gzip') 102 | self.assertEqual(entry.getencodedmimetype(), 'text/plain') 103 | self.assertEqual(entry.geteadict(), 104 | {'ABSTRACT': b"This is the abstract\nfor testfile.txt.gz"}) 105 | 106 | def testpopulate_dir(self): 107 | fspath = self.root + '/' 108 | entry = GopherEntry('/', self.config) 109 | entry.populatefromfs('/') 110 | 111 | conditions = { 112 | 'selector' : '/', 113 | 'config' : self.config, 114 | 'fspath' : '/', 115 | 'type' : '1', 116 | 'name' : '', 117 | 'host' : None, 118 | 'port' : None, 119 | 'mimetype' : 'application/gopher-menu', 120 | 'encodedmimetype' : None, 121 | 'encoding' : None, 122 | 'populated' : 1, 123 | 'language' : None, 124 | 'gopherpsupport' : 1} 125 | 126 | self.assertEntryMatches(conditions, entry, 127 | "testpopulate_dir") 128 | self.assertEqual(entry.geteadict(), 129 | {'ABSTRACT': 130 | b'This is the abstract for the testdata directory.'}) 131 | 132 | def testpopulate_remote(self): 133 | """Asserts that population is not done on remote objects.""" 134 | selector = '/testfile.txt' 135 | fspath = self.root + selector 136 | entry = GopherEntry(selector, self.config) 137 | entry.host = 'gopher.nowhere' 138 | entry.populatefromfs(fspath) 139 | assert entry.gettype() == None 140 | 141 | entry.populated = 0 142 | entry.host = None 143 | entry.port = 70 144 | entry.populatefromfs(fspath) 145 | assert entry.gettype() == None 146 | 147 | entry.populated = 0 148 | entry.host = 'gopher.nowhere' 149 | entry.populatefromfs(fspath) 150 | assert entry.gettype() == None 151 | 152 | def testpopulate_untouched(self): 153 | """Asserts that populatefromfs does not touch data that has already 154 | been set.""" 155 | 156 | selector = '/testfile.txt' 157 | fspath = selector 158 | 159 | entry = GopherEntry(selector, self.config) 160 | entry.name = 'FAKE NAME' 161 | entry.ctime = 1 162 | entry.mtime = 2 163 | entry.populatefromfs(fspath) 164 | self.assertEntryMatches({'name' : 'FAKE NAME', 'ctime':1, 'mtime':2}, 165 | entry, 'testpopulate_untouched') 166 | 167 | # Reset for the next batch. 168 | entry = GopherEntry('/', self.config) 169 | 170 | # Test type for a dir. 171 | entry.type = '2' 172 | entry.mimetype = 'FAKEMIMETYPE' 173 | entry.populatefromfs(self.root) 174 | self.assertEqual(entry.gettype(), '2') 175 | self.assertEqual(entry.getmimetype(), 'FAKEMIMETYPE') 176 | 177 | # Test mime type handling. First, regular file. 178 | 179 | entry = GopherEntry(selector, self.config) 180 | entry.mimetype = 'fakemimetype' 181 | entry.populatefromfs(fspath) 182 | self.assertEqual(entry.getmimetype(), 'fakemimetype') 183 | # The guesstype will not find fakemimetype and so it'll set it to 0 184 | self.assertEqual(entry.gettype(), '0') 185 | 186 | # Now, an encoded file. 187 | 188 | entry = GopherEntry(selector + '.gz', self.config) 189 | entry.mimetype = 'fakemime' 190 | entry.populatefromfs(fspath + '.gz') 191 | self.assertEqual(entry.getmimetype(), 'fakemime') 192 | self.assertEqual(entry.getencoding(), 'gzip') 193 | self.assertEqual(entry.getencodedmimetype(), 'text/plain') 194 | self.assertEqual(entry.gettype(), '0') # again from fakemime 195 | 196 | # More details. 197 | 198 | selector = '/testarchive.tgz' 199 | fspath = selector 200 | entry = GopherEntry(selector, self.config) 201 | entry.mimetype = 'foo1234' 202 | entry.encoding = 'bar' 203 | entry.populatefromfs(fspath) 204 | self.assertEqual(entry.getmimetype(), 'foo1234') 205 | self.assertEqual(entry.getencoding(), 'bar') 206 | self.assertEqual(entry.getencodedmimetype(), 'application/x-tar') 207 | self.assertEqual(entry.gettype(), '0') 208 | 209 | # And overriding only the encoding. 210 | 211 | entry = GopherEntry(selector, self.config) 212 | entry.encoding = 'fakeencoding' 213 | entry.populatefromfs(fspath) 214 | self.assertEqual(entry.getencoding(), 'fakeencoding') 215 | self.assertEqual(entry.gettype(), '9') 216 | self.assertEqual(entry.getmimetype(), 'application/octet-stream') 217 | 218 | # And finally -- overriding the encoded mime type. 219 | 220 | entry = GopherEntry(selector, self.config) 221 | entry.encodedmimetype = 'fakeencoded' 222 | entry.populatefromfs(fspath) 223 | self.assertEqual(entry.getencodedmimetype(), 'fakeencoded') 224 | self.assertEqual(entry.getmimetype(), 'application/octet-stream') 225 | 226 | def test_guesstype(self): 227 | entry = GopherEntry('/NONEXISTANT', self.config) 228 | expected = {'text/plain': '0', 229 | 'application/gopher-menu': '1', 230 | 'application/gopher+-menu' : '1', 231 | 'text/html' : 'h', 232 | 'image/gif' : 'g', 233 | 'image/jpeg' : 'I', 234 | 'application/pdf' : '9', 235 | 'application/msword' : '9', 236 | 'audio/aiff' : 's'} 237 | 238 | for mimetype, type in list(expected.items()): 239 | entry.mimetype = mimetype 240 | self.assertEqual(entry.guesstype(), type, 241 | "Failure for %s: got %s, expected %s" % \ 242 | (mimetype, entry.guesstype(), type)) 243 | 244 | def test_gets_sets(self): 245 | """Tests a bunch of gets that operate on values that are None 246 | to start with, and take a default.""" 247 | 248 | entry = GopherEntry('/NONEXISTANT', self.config) 249 | # Initialize the rest of them to None. 250 | entry.selector = None 251 | entry.config = None 252 | entry.populated = None 253 | entry.num = None 254 | entry.gopherpsupport = None 255 | 256 | for field in fields: 257 | getfunc = getattr(entry, 'get' + field) 258 | setfunc = getattr(entry, 'set' + field) 259 | self.assertEqual(getfunc(), None) 260 | self.assertEqual(getfunc('DEFAULT' + field), 'DEFAULT' + field) 261 | setfunc('NewValue' + field) 262 | self.assertEqual(getfunc(), 'NewValue' + field) 263 | self.assertEqual(getfunc('DEFAULT'), 'NewValue' + field) 264 | 265 | def testgeturl(self): 266 | expected = { 267 | '/URL:http://www.complete.org/%20/': 'http://www.complete.org/%20/', 268 | 'URL:telnet://foo.com/%20&foo=bar': 'telnet://foo.com/%20&foo=bar', 269 | '/foo' : 'gopher://MISSINGHOST:70/0/foo', 270 | '/About Me.txt' : 'gopher://MISSINGHOST:70/0/About%20Me.txt', 271 | '/' : 'gopher://MISSINGHOST:70/0/'} 272 | for selector, url in list(expected.items()): 273 | entry = GopherEntry(selector, self.config) 274 | entry.settype('0') 275 | self.assertEqual(url, entry.geturl()) 276 | self.assertEqual(re.sub('MISSINGHOST', 'NEWHOST', url), 277 | entry.geturl('NEWHOST')) 278 | self.assertEqual(re.sub('70', '10101', url), 279 | entry.geturl(defaultport = 10101)) 280 | entry.sethost('newhost') 281 | self.assertEqual(re.sub('MISSINGHOST', 'newhost', url), 282 | entry.geturl()) 283 | entry.setport(80) 284 | self.assertEqual(re.sub('MISSINGHOST:70', 'newhost:80', url), 285 | entry.geturl()) 286 | 287 | 288 | -------------------------------------------------------------------------------- /pygopherd/handlers/HandlerMultiplexer.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: find the right handler for a request 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | from pygopherd import GopherExceptions, logger 20 | from pygopherd.handlers import * 21 | import os, re 22 | 23 | handlers = None 24 | rootpath = None 25 | 26 | def getHandler(selector, searchrequest, protocol, config, handlerlist = None, 27 | vfs = None): 28 | """Called without handlerlist specified, uses the default as listed 29 | in config.""" 30 | global handlers, rootpath 31 | 32 | if vfs == None: 33 | from pygopherd.handlers.base import VFS_Real 34 | vfs = VFS_Real(config) 35 | 36 | if not handlers: 37 | handlers = eval(config.get("handlers.HandlerMultiplexer", 38 | "handlers")) 39 | rootpath = config.get("pygopherd", "root") 40 | if handlerlist == None: 41 | handlerlist = handlers 42 | 43 | # SECURITY: assert that our absolute path is within the absolute 44 | # path of the site root. 45 | 46 | #if not os.path.abspath(rootpath + '/' + selector). \ 47 | # startswith(os.path.abspath(rootpath)): 48 | # raise GopherExceptions.FileNotFound, \ 49 | # [selector, "Requested document is outside the server root", 50 | # protocol] 51 | 52 | statresult = None 53 | try: 54 | statresult = vfs.stat(selector) 55 | except OSError: 56 | pass 57 | for handler in handlerlist: 58 | htry = handler(selector, searchrequest, protocol, config, statresult, 59 | vfs) 60 | if htry.isrequestforme(): 61 | return htry.gethandler() 62 | 63 | raise GopherExceptions.FileNotFound([selector, "no handler found", protocol]) 64 | -------------------------------------------------------------------------------- /pygopherd/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: directory marker 3 | # Copyright (C) 2002, 2003 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | __all__ = ['base', 'dir', 'file', 'url', 'gophermap', 20 | 'UMN', 'ZIP', 'html', 'mbox', 'virtual', 'pyg', 'scriptexec', 21 | 'tal'] 22 | #import base, dir, file, gophermap, UMN, html, mbox, virtual, pyg 23 | #import scriptexec, url 24 | -------------------------------------------------------------------------------- /pygopherd/handlers/base.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: base handler code 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import socketserver 20 | import re 21 | import os, stat, os.path, mimetypes 22 | from pygopherd import protocols, gopherentry 23 | 24 | rootpath = None 25 | 26 | class VFS_Real: 27 | def __init__(self, config, chain = None): 28 | """This implementation does not chain.""" 29 | self.config = config 30 | 31 | def iswritable(self, selector): 32 | return 1 33 | 34 | def unlink(self, selector): 35 | os.unlink(self.getfspath(selector)) 36 | 37 | def stat(self, selector): 38 | return os.stat(self.getfspath(selector)) 39 | 40 | def isdir(self, selector): 41 | return os.path.isdir(self.getfspath(selector)) 42 | 43 | def isfile(self, selector): 44 | return os.path.isfile(self.getfspath(selector)) 45 | 46 | def exists(self, selector): 47 | return os.path.exists(self.getfspath(selector)) 48 | 49 | def open(self, selector, *args, **kwargs): 50 | return open(*(self.getfspath(selector),) + args, **kwargs) 51 | 52 | def listdir(self, selector): 53 | return os.listdir(self.getfspath(selector)) 54 | 55 | def getrootpath(self): 56 | global rootpath 57 | if not rootpath: 58 | rootpath = self.config.get("pygopherd", "root") 59 | return rootpath 60 | 61 | def getfspath(self, selector): 62 | """Gets the filesystem path corresponding to the selector.""" 63 | 64 | fspath = self.getrootpath() + selector 65 | # Strip off trailing slash. 66 | if fspath[-1] == '/': 67 | fspath = fspath[0:-1] 68 | 69 | return fspath 70 | 71 | def copyto(self, name, fd): 72 | rfile = self.open(name, 'rb') 73 | while 1: 74 | data = rfile.read(4096) 75 | if not len(data): 76 | break 77 | fd.write(data) 78 | rfile.close 79 | 80 | class BaseHandler: 81 | """Skeleton handler -- includes commonly-used routines.""" 82 | def __init__(self, selector, searchrequest, protocol, config, statresult, 83 | vfs = None): 84 | """Parameters are: 85 | selector -- requested selector. The selector must always start 86 | with a slash and never end with a slash UNLESS it is a one-char 87 | selector that contains only a slash. This should be handled 88 | by the default protocol. 89 | 90 | config -- config object.""" 91 | self.selector = selector 92 | self.searchrequest = searchrequest 93 | self.protocol = protocol 94 | self.config = config 95 | self.statresult = statresult 96 | self.fspath = None 97 | self.entry = None 98 | self.searchrequest = searchrequest 99 | if not vfs: 100 | self.vfs = VFS_Real(self.config) 101 | else: 102 | self.vfs = vfs 103 | 104 | def isrequestforme(self): 105 | """Called by multiplexers or other handlers. The default 106 | implementation is just: 107 | 108 | return self.isrequestsecure() and self.canhandlerequest() 109 | """ 110 | return self.isrequestsecure() and self.canhandlerequest() 111 | 112 | def isrequestsecure(self): 113 | """An auxiliary to canhandlerequest. In order for this handler 114 | to be selected for handling a given request, both the securitycheck 115 | and the canhandlerequest should be invoked. The securitycheck is 116 | intended to be a short, small, quick check -- usually not even 117 | looking at the filesystem. Here is a default. Returns true 118 | if the request is secure, false if not. By default, we eliminate 119 | ./, ../, and // This is split out from canhandlerequest becase 120 | it could be too easy to forget about it there.""" 121 | return (self.selector.find("./") == -1) and \ 122 | (self.selector.find("..") == -1) and \ 123 | (self.selector.find("//") == -1) and \ 124 | (self.selector.find(".\\") == -1) and \ 125 | (self.selector.find("\\\\") == -1) and \ 126 | (self.selector.find("\0") == -1) 127 | 128 | 129 | def canhandlerequest(self): 130 | """Decides whether or not a given request is valid for this 131 | handler. Should be overridden by all subclasses.""" 132 | return 0 133 | 134 | def getentry(self): 135 | """Returns an entry object for this request.""" 136 | if not self.entry: 137 | self.entry = gopherentry.GopherEntry(self.selector, self.config) 138 | return self.entry 139 | 140 | def getfspath(self): 141 | if not self.fspath: 142 | self.fspath = self.vfs.getfspath(self.getselector()) 143 | return self.fspath 144 | 145 | def getselector(self): 146 | """Returns the selector we are handling.""" 147 | return self.selector 148 | 149 | def gethandler(self): 150 | """Returns the handler to use to process this request. For all 151 | but special cases (rewriting handleres, for instance), this should 152 | return self.""" 153 | return self 154 | 155 | ## The next three are the publically-exposed interface -- the ones 156 | ## called by things other than handlers. 157 | 158 | def prepare(self): 159 | """Prepares for a write. Ie, opens a file. This is 160 | used so that the protocols can try to detect an error before 161 | transmitting a result. Must always be called before write.""" 162 | pass 163 | 164 | def isdir(self): 165 | """Returns true if this handler is handling a directory; false 166 | otherwise. Not valid unless prepare has been called.""" 167 | 168 | return 0 169 | 170 | def write(self, wfile): 171 | """Writes out the request if isdir() returns false. You should 172 | NOT call write if isdir() returns true! Should be overridden 173 | by files.""" 174 | if self.isdir(): 175 | raise Exception("Attempt to use write for a directory") 176 | 177 | def getdirlist(self): 178 | """Returns a list-like object (list, iterator, tuple, generator, etc) 179 | that contains as its elements the gopherentry objects corresponding 180 | to each item in the directory. Valid only if self.isdir() returns 181 | true.""" 182 | if not self.isdir(): 183 | raise Exception("Attempt to use getdir for a file.") 184 | return [] 185 | 186 | -------------------------------------------------------------------------------- /pygopherd/handlers/dir.py: -------------------------------------------------------------------------------- 1 | 2 | # pygopherd -- Gopher-based protocol server in Python 3 | # module: regular directory handling 4 | # Copyright (C) 2002 John Goerzen 5 | # 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; version 2 of the License. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | 20 | import socketserver 21 | import re 22 | import os, stat, os.path, mimetypes, time 23 | from pygopherd import protocols, gopherentry, handlers 24 | from pygopherd.handlers import base 25 | from stat import * 26 | import pickle 27 | 28 | cachetime = None 29 | cachefile = None 30 | 31 | class DirHandler(base.BaseHandler): 32 | def canhandlerequest(self): 33 | """We can handle the request if it's for a directory.""" 34 | return self.statresult and S_ISDIR(self.statresult[ST_MODE]) 35 | 36 | def getentry(self): 37 | if not self.entry: 38 | self.entry = gopherentry.GopherEntry(self.selector, self.config) 39 | self.entry.populatefromfs(self.getselector(), self.statresult, vfs = self.vfs) 40 | return self.entry 41 | 42 | def prep_initfiles(self): 43 | "Initialize the list of files. Ignore the files we're suppoed to." 44 | self.files = [] 45 | dirfiles = self.vfs.listdir(self.getselector()) 46 | ignorepatt = self.config.get("handlers.dir.DirHandler", "ignorepatt") 47 | for file in dirfiles: 48 | if self.prep_initfiles_canaddfile(ignorepatt, 49 | self.selectorbase + '/' + file, 50 | file): 51 | self.files.append(file) 52 | 53 | def prep_initfiles_canaddfile(self, ignorepatt, pattern, file): 54 | return not re.search(ignorepatt, pattern) 55 | 56 | def prep_entries(self): 57 | "Generate entries from the list." 58 | 59 | self.fileentries = [] 60 | for file in self.files: 61 | # We look up the appropriate handler for this object, and ask 62 | # it to give us an entry object. 63 | handler = handlers.HandlerMultiplexer.\ 64 | getHandler(self.selectorbase + '/' \ 65 | + file, self.searchrequest, self.protocol, 66 | self.config, vfs = self.vfs) 67 | fileentry = handler.getentry() 68 | self.prep_entriesappend(file, handler, fileentry) 69 | 70 | def prep_entriesappend(self, file, handler, fileentry): 71 | """Subclasses can override to do post-processing on the entry while 72 | we still have the filename around. 73 | IE, for .cap files.""" 74 | self.fileentries.append(fileentry) 75 | 76 | def prepare(self): 77 | # Initialize some variables. 78 | 79 | self.selectorbase = self.selector 80 | if self.selectorbase == '/': 81 | self.selectorbase = '' # Avoid dup slashes 82 | 83 | if self.loadcache(): 84 | # No need to do anything else. 85 | return 0 # Did nothing. 86 | 87 | self.prep_initfiles() 88 | 89 | # Sort the list. 90 | self.files.sort() 91 | 92 | self.prep_entries() 93 | return 1 # Did something. 94 | 95 | def isdir(self): 96 | return 1 97 | 98 | def getdirlist(self): 99 | self.savecache() 100 | return self.fileentries 101 | 102 | def loadcache(self): 103 | global cachetime, cachefile 104 | 105 | self.fromcache = 0 106 | if cachetime == None: 107 | cachetime = self.config.getint("handlers.dir.DirHandler", 108 | "cachetime") 109 | cachefile = self.config.get("handlers.dir.DirHandler", 110 | "cachefile") 111 | cachename = self.selector + "/" + cachefile 112 | if not self.vfs.iswritable(cachename): 113 | return 0 114 | 115 | try: 116 | statval = self.vfs.stat(cachename) 117 | except OSError: 118 | return 0 119 | 120 | if (time.time() - statval[stat.ST_MTIME] < cachetime): 121 | fp = self.vfs.open(cachename, "rb") 122 | self.fileentries = pickle.load(fp) 123 | fp.close() 124 | self.fromcache = 1 125 | return 1 126 | return 0 127 | 128 | def savecache(self): 129 | global cachefile 130 | if self.fromcache: 131 | # Don't resave the cache. 132 | return 133 | if not self.vfs.iswritable(self.selector + "/" + cachefile): 134 | return 135 | try: 136 | fp = self.vfs.open(self.selector + "/" + cachefile, "wb") 137 | pickle.dump(self.fileentries, fp, 1) 138 | fp.close() 139 | except IOError: 140 | pass 141 | 142 | 143 | -------------------------------------------------------------------------------- /pygopherd/handlers/file.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: regular file handling 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import socketserver 20 | import re 21 | import os, stat, os.path, mimetypes 22 | from pygopherd import protocols, gopherentry 23 | from pygopherd.handlers import base 24 | import pygopherd.pipe 25 | from stat import * 26 | 27 | class FileHandler(base.BaseHandler): 28 | def canhandlerequest(self): 29 | """We can handle the request if it's for a file.""" 30 | return self.statresult and S_ISREG(self.statresult[ST_MODE]) 31 | 32 | def getentry(self): 33 | if not self.entry: 34 | self.entry = gopherentry.GopherEntry(self.selector, self.config) 35 | self.entry.populatefromfs(self.getselector(), self.statresult, vfs = self.vfs) 36 | return self.entry 37 | 38 | def write(self, wfile): 39 | self.vfs.copyto(self.getselector(), wfile) 40 | 41 | decompressors = None 42 | decompresspatt = None 43 | 44 | class CompressedFileHandler(FileHandler): 45 | def canhandlerequest(self): 46 | self.initdecompressors() 47 | 48 | # It's OK to call just canhandlerequest() since we're not 49 | # overriding the security or isrequestforme functions. 50 | 51 | return FileHandler.canhandlerequest(self) and \ 52 | self.getentry().realencoding and \ 53 | self.getentry().realencoding in decompressors and \ 54 | re.search(decompresspatt, self.selector) 55 | 56 | def getentry(self): 57 | if not self.entry: 58 | self.entry = FileHandler.getentry(self) 59 | self.entry.realencoding = None 60 | if self.entry.getencoding() and \ 61 | self.entry.getencoding() in decompressors and \ 62 | self.entry.getencodedmimetype(): 63 | # When the client gets it, there will not be 64 | # encoding. Therefore, we remove the encoding and switch 65 | # to the real MIME type. 66 | self.entry.mimetype = self.entry.getencodedmimetype() 67 | self.entry.encodedmimetype = None 68 | self.entry.realencoding = self.entry.encoding 69 | self.entry.encoding = None 70 | self.entry.type = self.entry.guesstype() 71 | return self.entry 72 | 73 | def initdecompressors(self): 74 | global decompressors, decompresspatt 75 | if decompressors == None: 76 | decompressors = \ 77 | eval(self.config.get("handlers.file.CompressedFileHandler", 78 | "decompressors")) 79 | decompresspatt = \ 80 | self.config.get("handlers.file.CompressedFileHandler", 81 | "decompresspatt") 82 | 83 | def write(self, wfile): 84 | global decompressors 85 | decompprog = decompressors[self.getentry().realencoding] 86 | pygopherd.pipe.pipedata_unix(decompprog, [decompprog], 87 | childstdin = self.rfile, 88 | childstdout = wfile, 89 | pathsearch = 1) 90 | self.rfile.close() 91 | self.rfile = None 92 | 93 | -------------------------------------------------------------------------------- /pygopherd/handlers/gophermap.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: Handling of gophermap directory files 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import socketserver 20 | import re 21 | import os, stat, os.path, mimetypes 22 | from pygopherd import protocols, gopherentry 23 | from pygopherd.handlers import base 24 | from stat import * 25 | 26 | class BuckGophermapHandler(base.BaseHandler): 27 | """Bucktooth selector handler. Adheres to the specification 28 | at gopher://gopher.floodgap.com:70/0/buck/dbrowse%3Ffaquse%201""" 29 | def canhandlerequest(self): 30 | """We can handle the request if it's for a directory AND 31 | the directory has a gophermap file.""" 32 | return self.statresult and ((S_ISDIR(self.statresult[ST_MODE]) and \ 33 | self.vfs.isfile(self.getselector() + '/gophermap')) or \ 34 | (S_ISREG(self.statresult[ST_MODE]) and \ 35 | self.getselector().endswith(".gophermap"))) 36 | 37 | def getentry(self): 38 | if not self.entry: 39 | self.entry = gopherentry.GopherEntry(self.selector, self.config) 40 | if (self.statresult and S_ISREG(self.statresult[ST_MODE]) and \ 41 | self.getselector().endswith(".gophermap")): 42 | self.entry.populatefromvfs(self.vfs, self.getselector()) 43 | else: 44 | self.entry.populatefromfs(self.getselector(), self.statresult, vfs = self.vfs) 45 | 46 | return self.entry 47 | 48 | def prepare(self): 49 | self.selectorbase = self.selector 50 | if self.selectorbase == '/': 51 | self.selectorbase = '' # Avoid dup slashes 52 | 53 | if self.getselector().endswith(".gophermap") and \ 54 | self.statresult and S_ISREG(self.statresult[ST_MODE]): 55 | self.rfile = self.vfs.open(self.getselector(), 'rb') 56 | else: 57 | self.rfile = self.vfs.open(self.selectorbase + '/gophermap', 'rb') 58 | 59 | self.entries = [] 60 | 61 | selectorbase = self.selectorbase 62 | 63 | while 1: 64 | line = self.rfile.readline() 65 | if not line: 66 | break 67 | if re.search("\t", line): # gophermap link 68 | args = [arg.strip() for arg in line.split("\t")] 69 | 70 | if len(args) < 2 or not len(args[1]): 71 | args[1] = args[0][1:] # Copy display string to selector 72 | 73 | selector = args[1] 74 | if selector[0] != '/' and selector[0:4] != "URL:": # Relative link 75 | selector = selectorbase + '/' + selector 76 | 77 | entry = gopherentry.GopherEntry(selector, self.config) 78 | entry.type = args[0][0] 79 | entry.name = args[0][1:] 80 | 81 | if len(args) >= 3 and len(args[2]): 82 | entry.host = args[2] 83 | 84 | if len(args) >= 4 and len(args[3]): 85 | entry.port = int(args[3]) 86 | 87 | if entry.gethost() == None and entry.getport() == None: 88 | # If we're using links on THIS server, try to fill 89 | # it in for gopher+. 90 | if self.vfs.exists(selector): 91 | entry.populatefromvfs(self.vfs, selector) 92 | self.entries.append(entry) 93 | else: # Info line 94 | line = line.strip() 95 | self.entries.append(gopherentry.getinfoentry(line, self.config)) 96 | 97 | def isdir(self): 98 | return 1 99 | 100 | def getdirlist(self): 101 | return self.entries 102 | 103 | -------------------------------------------------------------------------------- /pygopherd/handlers/html.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: Special handling of HTML files 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | from pygopherd.handlers.file import FileHandler 20 | import html.parser 21 | import socketserver 22 | import re 23 | import os, stat, os.path, mimetypes 24 | from pygopherd import protocols, gopherentry 25 | from pygopherd.gopherentry import GopherEntry 26 | import html.entities 27 | from stat import * 28 | 29 | ########################################################################### 30 | # HTML File Handler 31 | # Sets the name of a file if it's HTML. 32 | ########################################################################### 33 | class HTMLTitleParser(html.parser.HTMLParser): 34 | def __init__(self): 35 | html.parser.HTMLParser.__init__(self) 36 | self.titlestr = "" 37 | self.readingtitle = 0 38 | self.gotcompletetitle = 0 39 | 40 | def handle_starttag(self, tag, attrs): 41 | if tag == 'title': 42 | self.readingtitle = 1 43 | 44 | def handle_endtag(self, tag): 45 | if tag == 'title': 46 | self.gotcompletetitle = 1 47 | self.readingtitle = 0 48 | 49 | def handle_data(self, data): 50 | if self.readingtitle: 51 | self.titlestr += data 52 | 53 | def handle_entityref(self, name): 54 | """Handle things like & or > or <. If it's not in 55 | the dictionary, ignore it.""" 56 | if self.readingtitle and name in html.entities.entitydefs: 57 | self.titlestr += html.entities.entitydefs[name] 58 | 59 | class HTMLFileTitleHandler(FileHandler): 60 | """This class will set the title of a HTML document based on the 61 | HTML title. It is a clone of the UMN gsfindhtmltitle function.""" 62 | def canhandlerequest(self): 63 | if FileHandler.canhandlerequest(self): 64 | mimetype, encoding = mimetypes.guess_type(self.selector) 65 | return mimetype == 'text/html' 66 | else: 67 | return 0 68 | 69 | def getentry(self): 70 | # Start with the entry from the parent. 71 | entry = FileHandler.getentry(self) 72 | parser = HTMLTitleParser() 73 | file = self.vfs.open(self.getselector(), "rt") 74 | try: 75 | while not parser.gotcompletetitle: 76 | line = file.readline() 77 | if not line: 78 | break 79 | parser.feed(line) 80 | parser.close() 81 | except html.parser.HTMLParseError: 82 | # Parse error? Stop parsing, go to here. We can still 83 | # return a title if the parse error happened after we got 84 | # the title. 85 | pass 86 | 87 | file.close() 88 | # OK, we've parsed the file and exited because of either an EOF 89 | # or a complete title (or error). Now, figure out what happened. 90 | 91 | if parser.gotcompletetitle: 92 | # Convert all whitespace sequences to a single space. 93 | # Removes newlines, tabs, etc. Good for presentation 94 | # and for security. 95 | title = re.sub('[\s]+', ' ', parser.titlestr) 96 | entry.setname(title) 97 | return entry 98 | 99 | -------------------------------------------------------------------------------- /pygopherd/handlers/mbox.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: Present a mbox file as if it were a folder. 3 | # Copyright (C) 2002, 2005 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | 20 | import socketserver 21 | import re 22 | import os, stat, os.path, mimetypes 23 | from pygopherd import protocols, gopherentry 24 | from pygopherd.handlers.virtual import Virtual 25 | from pygopherd.handlers.base import VFS_Real 26 | from mailbox import mbox, Maildir 27 | from stat import * 28 | 29 | 30 | ########################################################################### 31 | # Basic mailbox support 32 | ########################################################################### 33 | 34 | class FolderHandler(Virtual): 35 | def getentry(self): 36 | ## Return my own entry. 37 | if not self.entry: 38 | self.entry = gopherentry.GopherEntry(self.getselector(), 39 | self.config) 40 | self.entry.settype('1') 41 | self.entry.setname(os.path.basename(self.getselector())) 42 | self.entry.setmimetype('application/gopher-menu') 43 | self.entry.setgopherpsupport(0) 44 | return self.entry 45 | 46 | def prepare(self): 47 | self.entries = [] 48 | count = 1 49 | while 1: 50 | message = next(self.mbox) 51 | if not message: 52 | break 53 | handler = MessageHandler(self.genargsselector(self.getargflag() + \ 54 | str(count)), self.searchrequest, 55 | self.protocol, self.config, None) 56 | self.entries.append(handler.getentry(message)) 57 | count += 1 58 | 59 | def isdir(self): 60 | return 1 61 | 62 | def getdirlist(self): 63 | return self.entries 64 | 65 | class MessageHandler(Virtual): 66 | def canhandlerequest(self): 67 | """We put MBOX-MESSAGE in here so we don't have to re-check 68 | the first line of the mbox file before returning a true or false 69 | result.""" 70 | if not self.selectorargs: 71 | return 0 72 | msgnum = re.search('^' + self.getargflag() + '(\d+)$', 73 | self.selectorargs) 74 | if not msgnum: 75 | return 0 76 | self.msgnum = int(msgnum.group(1)) 77 | self.message = None 78 | return 1 79 | 80 | def getentry(self, message = None): 81 | """Set the message if called from, eg, the dir handler. Saves 82 | having to rescan the file. If not set, will figure it out.""" 83 | if not message: 84 | message = self.getmessage() 85 | 86 | if not self.entry: 87 | self.entry = gopherentry.GopherEntry(self.selector, self.config) 88 | self.entry.settype('0') 89 | self.entry.setmimetype('text/plain') 90 | self.entry.setgopherpsupport(0) 91 | 92 | subject = message.getheader('Subject', '') 93 | # Sanitize, esp. for continuations. 94 | subject = re.sub('\s+', ' ', subject) 95 | if subject: 96 | self.entry.setname(subject) 97 | else: 98 | self.entry.setname('') 99 | return self.entry 100 | 101 | def getmessage(self): 102 | if self.message: 103 | return self.message 104 | mbox = self.openmailbox() 105 | message = None 106 | for x in range(self.msgnum): 107 | message = next(mbox) 108 | self.message = message 109 | return self.message 110 | 111 | def prepare(self): 112 | self.canhandlerequest() # Init the vars 113 | 114 | def write(self, wfile): 115 | # Print out the headers first. 116 | for header in self.getmessage().headers: 117 | wfile.write(header) 118 | 119 | # Now the message body. 120 | self.rfile = self.getmessage().fp 121 | while 1: 122 | string = self.rfile.read(4096) 123 | if not len(string): 124 | break 125 | wfile.write(string) 126 | self.rfile.close() 127 | self.rfile = None 128 | 129 | ########################################################################### 130 | # Unix MBOX support 131 | ########################################################################### 132 | 133 | class MBoxFolderHandler(FolderHandler): 134 | def canhandlerequest(self): 135 | """Figure out if this is a handleable request.""" 136 | 137 | if not isinstance(self.vfs, VFS_Real): 138 | return 0 139 | if self.selectorargs: 140 | return 0 141 | if not (self.statresult and S_ISREG(self.statresult[ST_MODE])): 142 | return 0 143 | try: 144 | fd = self.vfs.open(self.getselector(), "rb") 145 | startline = fd.readline() 146 | fd.close() 147 | 148 | # From old Python2.7 UnixMailbox 149 | fromlinepattern = (rb"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" 150 | rb"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*" 151 | rb"[^\s]*\s*" 152 | b"$") 153 | 154 | return re.match(fromlinepattern, startline) 155 | except IOError: 156 | return 0 157 | 158 | def prepare(self): 159 | self.mbox = mbox(self.getfspath()) 160 | FolderHandler.prepare(self) 161 | 162 | def getargflag(self): 163 | return "/MBOX-MESSAGE/" 164 | 165 | class MBoxMessageHandler(MessageHandler): 166 | def getargflag(self): 167 | return "/MBOX-MESSAGE/" 168 | 169 | def openmailbox(self): 170 | fd = self.vfs.open(self.getselector(), "rt") 171 | return UnixMailbox(fd) 172 | 173 | ########################################################################### 174 | # Maildir support 175 | ########################################################################### 176 | 177 | class MaildirFolderHandler(FolderHandler): 178 | def canhandlerequest(self): 179 | if not isinstance(self.vfs, VFS_Real): 180 | return 0 181 | if self.selectorargs: 182 | return 0 183 | if not (self.statresult and S_ISDIR(self.statresult[ST_MODE])): 184 | return 0 185 | return self.vfs.isdir(self.getselector() + "/new") and \ 186 | self.vfs.isdir(self.getselector() + "/cur") 187 | 188 | def prepare(self): 189 | self.mbox = Maildir(self.getfspath()) 190 | FolderHandler.prepare(self) 191 | 192 | def getargflag(self): 193 | return "/MAILDIR-MESSAGE/" 194 | 195 | class MaildirMessageHandler(MessageHandler): 196 | def getargflag(self): 197 | return "/MAILDIR-MESSAGE/" 198 | 199 | def openmailbox(self): 200 | return Maildir(self.getfspath()) 201 | -------------------------------------------------------------------------------- /pygopherd/handlers/pyg.py: -------------------------------------------------------------------------------- 1 | from pygopherd import protocols, gopherentry 2 | from pygopherd.handlers.base import BaseHandler, VFS_Real 3 | from pygopherd.handlers.virtual import Virtual 4 | from stat import * 5 | import imp, re 6 | 7 | class PYGHandler(Virtual): 8 | def canhandlerequest(self): 9 | if not isinstance(self.vfs, VFS_Real): 10 | return 0 11 | if not (self.statresult and S_ISREG(self.statresult[ST_MODE]) and \ 12 | (S_IMODE(self.statresult[ST_MODE]) & S_IXOTH) and \ 13 | re.search("\.pyg$", self.getselector())): 14 | return 0 15 | self.modfd = self.vfs.open(self.getselector(), "rt") 16 | self.module = imp.load_module('PYGHandler', 17 | self.modfd, 18 | self.getfspath(), 19 | ('', '', imp.PY_SOURCE)) 20 | self.pygclass = self.module.PYGMain 21 | self.pygobject = self.pygclass(self.selector, self.searchrequest, 22 | self.protocol, 23 | self.config, self.statresult) 24 | return self.pygobject.isrequestforme() 25 | 26 | def prepare(self): 27 | return self.pygobject.prepare() 28 | 29 | def getentry(self): 30 | return self.pygobject.getentry() 31 | 32 | def isdir(self): 33 | return self.pygobject.isdir() 34 | 35 | def getdirlist(self): 36 | return self.pygobject.getdirlist() 37 | 38 | def write(self, wfile): 39 | self.pygobject.write(wfile) 40 | 41 | class PYGBase(Virtual): 42 | pass 43 | -------------------------------------------------------------------------------- /pygopherd/handlers/scriptexec.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: Script execution 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | 20 | from pygopherd import protocols, gopherentry 21 | from pygopherd.handlers.base import BaseHandler, VFS_Real 22 | from pygopherd.handlers.virtual import Virtual 23 | import pygopherd.pipe 24 | from stat import * 25 | import imp, re, os 26 | 27 | class ExecHandler(Virtual): 28 | def canhandlerequest(self): 29 | # We ONLY handle requests from the real filesystem. 30 | return isinstance(self.vfs, VFS_Real) and \ 31 | self.statresult and S_ISREG(self.statresult[ST_MODE]) and \ 32 | (S_IMODE(self.statresult[ST_MODE]) & S_IXOTH) 33 | 34 | def getentry(self): 35 | entry = gopherentry.GopherEntry(self.getselector(), self.config) 36 | entry.settype('0') 37 | entry.setname(os.path.basename(self.getselector())) 38 | entry.setmimetype('text/plain') 39 | entry.setgopherpsupport(0) 40 | return entry 41 | 42 | def write(self, wfile): 43 | # We work on a separate thing to avoid contaminating our own 44 | # environment. Just saying newenv = os.environ would still 45 | # do that. 46 | newenv = {} 47 | for key in list(os.environ.keys()): 48 | newenv[key] = os.environ[key] 49 | newenv['SERVER_NAME'] = self.protocol.server.server_name 50 | newenv['SERVER_PORT'] = str(self.protocol.server.server_port) 51 | newenv['REMOTE_ADDR'] = self.protocol.requesthandler.client_address[0] 52 | newenv['REMOTE_PORT'] = str(self.protocol.requesthandler.client_address[1]) 53 | newenv['REMOTE_HOST'] = newenv['REMOTE_ADDR'] 54 | newenv['SELECTOR'] = self.selector 55 | newenv['REQUEST'] = self.getselector() 56 | if self.searchrequest: 57 | newenv['SEARCHREQUEST'] = self.searchrequest 58 | wfile.flush() 59 | 60 | args = [self.getfspath()] 61 | if self.selectorargs: 62 | args.extend(self.selectorags.split(' ')) 63 | 64 | pygopherd.pipe.pipedata(self.getfspath(), args, newenv, 65 | childstdout = wfile) 66 | -------------------------------------------------------------------------------- /pygopherd/handlers/tal.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: TAL file handling. 3 | # Copyright (C) 2002, 2003 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | try: 20 | from simpletal import simpleTAL, simpleTALES 21 | talavailable = 1 22 | except: 23 | talavailable = 0 24 | 25 | try: 26 | import logging 27 | haslogging = 1 28 | except: 29 | haslogging = 0 30 | 31 | if haslogging: 32 | import os 33 | try: 34 | hdlrFilename = os.path.join(os.environ['TEMP'], 'mylog.log') 35 | except: 36 | hdlrFilename = '/tmp/mylog.log' 37 | logger = logging.getLogger('simpleTAL.HTMLTemplateCompiler') 38 | hdlr = logging.FileHandler(hdlrFilename) 39 | formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') 40 | hdlr.setFormatter(formatter) 41 | logger.addHandler(hdlr) 42 | logger.setLevel(logging.INFO) 43 | 44 | 45 | from pygopherd.handlers.file import FileHandler 46 | from pygopherd import gopherentry 47 | import re, os.path 48 | 49 | class TALLoader: 50 | def __init__(self, vfs, path): 51 | self.vfs = vfs 52 | self.path = path 53 | 54 | def getpath(self): 55 | return self.path 56 | 57 | def getparent(self): 58 | if self.path == '/': 59 | return self 60 | else: 61 | return self.__class__(self.vfs, os.path.dirname(self.path)) 62 | 63 | def getchildrennames(self): 64 | return self.vfs.listdir(self.path) 65 | 66 | #def getchildren(self): 67 | # return [self.__class__(self.vfs, os.path.join(self.path, item)) \ 68 | # for item in self.getchildrennames()] 69 | 70 | 71 | def __getattr__(self, key): 72 | fq = os.path.join(self.path, key) 73 | if self.vfs.isfile(fq + ".html.tal"): 74 | templateFile = self.vfs.open(fq + ".html.tal") 75 | compiled = simpleTAL.compileHTMLTemplate(templateFile) 76 | templateFile.close() 77 | return compiled 78 | elif self.vfs.isdir(fq): 79 | return self.__class__(self.vfs, fq) 80 | else: 81 | raise AttributeError("Key %s not found in %s" % (key, self.path)) 82 | 83 | class RecursiveTALLoader(TALLoader): 84 | def __getattr__(self, key): 85 | if self.path == '/': 86 | # Already at the top -- can't recurse. 87 | return TALLoader.__getattr__(self, key) 88 | try: 89 | return TALLoader.__getattr__(self, key) 90 | except AttributeError: 91 | return self.getparent().__getattr__(self, key) 92 | 93 | class TALFileHandler(FileHandler): 94 | def canhandlerequest(self): 95 | """We can handle the request if it's for a file ending with .thtml.""" 96 | canhandle = FileHandler.canhandlerequest(self) and self.getselector().endswith(".tal") 97 | if not canhandle: 98 | return 0 99 | self.talbasename = self.getselector()[:-4] 100 | self.allowpythonpath = 1 101 | if self.config.has_option('handlers.tal.TALFileHandler', 'allowpythonpath'): 102 | self.allowpythonpath = self.config.getboolean('handlers.tal.TALFileHandler', 'allowpythonpath') 103 | return 1 104 | 105 | def getentry(self): 106 | if not self.entry: 107 | self.entry = gopherentry.GopherEntry(self.selector, self.config) 108 | self.entry.populatefromfs(self.getselector(), self.statresult, vfs = self.vfs) 109 | assert self.entry.getencoding() == 'tal.TALFileHandler' 110 | # Remove the TAL encoding and revert to default. 111 | self.entry.mimetype = self.entry.getencodedmimetype() 112 | self.entry.encodedmimetype = None 113 | self.entry.realencoding = self.entry.encoding 114 | self.entry.encoding = None 115 | self.entry.type = self.entry.guesstype() 116 | 117 | return self.entry 118 | 119 | def write(self, wfile): 120 | rfile = self.vfs.open(self.getselector()) 121 | context = simpleTALES.Context(allowPythonPath = self.allowpythonpath) 122 | context.addGlobal("selector", self.getselector()) 123 | context.addGlobal('handler', self) 124 | context.addGlobal('entry', self.getentry()) 125 | context.addGlobal('talbasename', self.talbasename) 126 | context.addGlobal('allowpythonpath', self.allowpythonpath) 127 | context.addGlobal('protocol', self.protocol) 128 | context.addGlobal('root', TALLoader(self.vfs, '/')) 129 | context.addGlobal('rroot', RecursiveTALLoader(self.vfs, '/')) 130 | dirname = os.path.dirname(self.getselector()) 131 | context.addGlobal('dir', TALLoader(self.vfs, dirname)) 132 | context.addGlobal('rdir', RecursiveTALLoader(self.vfs, dirname)) 133 | 134 | template = simpleTAL.compileHTMLTemplate(rfile) 135 | rfile.close() 136 | template.expand(context, wfile) 137 | -------------------------------------------------------------------------------- /pygopherd/handlers/url.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: Special handling for URLs 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import socketserver 20 | import re 21 | import os, stat, os.path, mimetypes 22 | from pygopherd import protocols, gopherentry, handlers 23 | from pygopherd.handlers.base import BaseHandler 24 | 25 | class HTMLURLHandler(BaseHandler): 26 | """Will take requests for a URL-like selector and generate 27 | a HTML page redirecting people to the actual URL. 28 | 29 | This implementation adheres to the proposal as specified at 30 | http://www.complete.org/mailinglists/archives/gopher-200202/msg00033.html 31 | """ 32 | 33 | def isrequestsecure(self): 34 | """For URLs, it is valid to have .., //, etc in the URLs.""" 35 | return self.canhandlerequest() and \ 36 | self.selector.find("\0") == -1 and \ 37 | self.selector.find("\n") == -1 and \ 38 | self.selector.find("\t") == -1 and \ 39 | self.selector.find('"') == -1 and \ 40 | self.selector.find("\r") == -1 41 | 42 | def canhandlerequest(self): 43 | """We can handle the request if it's for something that starts 44 | with http or https.""" 45 | return re.search("^(/|)URL:.+://", self.selector) 46 | 47 | def getentry(self): 48 | if not self.entry: 49 | self.entry = gopherentry.GopherEntry(self.selector, self.config) 50 | self.entry.name = self.selector 51 | self.entry.mimetype = 'text/html' 52 | self.entry.type = 'h' 53 | return self.entry 54 | 55 | # We have nothing to prepare. 56 | 57 | def write(self, wfile): 58 | url = self.selector[4:] # Strip off URL: 59 | if self.selector[0] == '/': 60 | url = self.selector[5:] 61 | outdoc = "\n" 62 | outdoc += '' % url 63 | outdoc += "\n" 64 | outdoc += """ 65 | You are following a link from gopher to a website. You will be 66 | automatically taken to the web site shortly. If you do not get 67 | sent there, please click """ 68 | outdoc += '
here ' % url 69 | outdoc += """to go to the web site. 70 |

71 | The URL linked is: 72 |

""" 73 | outdoc += '%s' % (url, url) 74 | outdoc += """

75 | Thanks for using gopher! 76 |

77 | Document generated by pygopherd handlers.url.HTMLURLHandler 78 | """ 79 | wfile.write(outdoc) 80 | 81 | class URLTypeRewriter(BaseHandler): 82 | """Will take URLs that start with a file type (ie, 83 | /1/devel/offlineimap) and remove the type (/devel/offlineimap). Useful 84 | if you want to make relative links in both gopher and http space in 85 | a single document.""" 86 | 87 | def canhandlerequest(self): 88 | return len(self.selector) >= 3 and \ 89 | self.selector[0] == '/' and \ 90 | self.selector[2] == '/' 91 | 92 | def gethandler(self): 93 | handlerlist = [x for x in handlers.HandlerMultiplexer.handlers if 94 | x != URLTypeRewriter] 95 | return handlers.HandlerMultiplexer.getHandler(self.selector[2:], 96 | self.searchrequest, self.protocol, 97 | self.config, handlerlist) 98 | 99 | -------------------------------------------------------------------------------- /pygopherd/handlers/virtual.py: -------------------------------------------------------------------------------- 1 | from pygopherd import gopherentry 2 | from stat import * 3 | from pygopherd.handlers.base import BaseHandler 4 | import os 5 | 6 | class Virtual(BaseHandler): 7 | """Implementation of virtual folder support. This class will probably 8 | not be instantiated itself but it is designed to be instantiated by 9 | its children.""" 10 | 11 | def __init__(self, selector, searchrequest, protocol, config, statresult, 12 | vfs = None): 13 | BaseHandler.__init__(self, selector, searchrequest, 14 | protocol, config, statresult, vfs) 15 | 16 | # These hold the "real" and the "argument" portion of the selector, 17 | # respectively. 18 | 19 | self.selectorreal = None 20 | self.selectorargs = None 21 | 22 | if self.selector.find("?") != -1 or self.selector.find("|") != -1: 23 | try: 24 | i = self.selector.index("?") 25 | except: 26 | i = self.selector.index("|") 27 | 28 | self.selectorreal = self.selector[0:i] 29 | self.selectorargs = self.selector[i+1:] 30 | # Now, retry the stat with the real selector. 31 | self.statresult = None 32 | try: 33 | self.statresult = self.vfs.stat(self.selectorreal) 34 | except OSError: 35 | pass 36 | else: 37 | # Best guess. 38 | self.selectorreal = self.selector 39 | 40 | 41 | def genargsselector(self, args): 42 | """Returns a string representing a full selector to this resource, with 43 | the given string of args. This is a selector that can be passed 44 | back to clients.""" 45 | return self.getselector() + "|" + args 46 | 47 | def getselector(self): 48 | """Overridden to return the 'real' portion of the selector.""" 49 | return self.selectorreal 50 | 51 | -------------------------------------------------------------------------------- /pygopherd/initialization.py: -------------------------------------------------------------------------------- 1 | # Python-based gopher server 2 | # Module: initialization 3 | # COPYRIGHT # 4 | # Copyright (C) 2002 John Goerzen 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | # END OF COPYRIGHT # 19 | 20 | from configparser import ConfigParser 21 | 22 | # Import lots of stuff so it's here before chrooting. 23 | import socket, os, sys, socketserver, re, stat, os.path, tempfile 24 | import time, atexit, errno, struct 25 | 26 | from pygopherd import handlers, protocols, GopherExceptions, logger, sighandlers 27 | from pygopherd.protocols import * 28 | from pygopherd.protocols import ProtocolMultiplexer 29 | from pygopherd.handlers import * 30 | from pygopherd.handlers import HandlerMultiplexer 31 | import pygopherd.fileext 32 | import mimetypes 33 | 34 | import traceback 35 | 36 | 37 | def initconffile(conffile): 38 | if not (os.path.isfile(conffile) and os.access(conffile, os.R_OK)): 39 | raise Exception("Could NOT access config file %s\nPlease specify config file as a command-line argument\n" % conffile) 40 | 41 | config = ConfigParser() 42 | config.read(conffile) 43 | return config 44 | 45 | def initlogger(config, conffile): 46 | logger.init(config) 47 | logger.log("Pygopherd starting, using configuration file %s" % conffile) 48 | 49 | def initexceptions(config): 50 | GopherExceptions.init(config.getboolean("pygopherd", "tracebacks")) 51 | 52 | def initmimetypes(config): 53 | mimetypesfiles = config.get("pygopherd", "mimetypes").split(":") 54 | mimetypesfiles = [x for x in mimetypesfiles if os.path.isfile(x) and os.access(x, os.R_OK)] 55 | 56 | if not mimetypesfiles: 57 | errmsg = "Could not find any mimetypes files; check mimetypes option in config." 58 | logger.log(errmsg) 59 | raise Exception(errmsg) 60 | 61 | 62 | configencoding = eval(config.get("pygopherd", "encoding")) 63 | mimetypes.encodings_map.clear() 64 | for key, value in configencoding: 65 | mimetypes.encodings_map[key] = value 66 | mimetypes.init(mimetypesfiles) 67 | logger.log("mimetypes initialized with files: " + str(mimetypesfiles)) 68 | 69 | # Set up the inverse mapping file. 70 | 71 | pygopherd.fileext.init() 72 | 73 | class GopherRequestHandler(socketserver.StreamRequestHandler): 74 | def handle(self): 75 | request = self.rfile.readline() 76 | 77 | protohandler = \ 78 | ProtocolMultiplexer.getProtocol(request, \ 79 | self.server, self, self.rfile, self.wfile, self.server.config) 80 | try: 81 | protohandler.handle() 82 | except socket.error as e: 83 | if not (e[0] in [errno.ECONNRESET, errno.EPIPE]): 84 | traceback.print_exc() 85 | GopherExceptions.log(sys.exc_info()[1], protohandler, None) 86 | except: 87 | if GopherExceptions.tracebacks: 88 | # Yes, this may be invalid. Not much else we can do. 89 | #traceback.print_exc(file = self.wfile) 90 | traceback.print_exc() 91 | GopherExceptions.log(sys.exc_info()[1], protohandler, None) 92 | 93 | def getserverobject(config): 94 | # Pick up the server type from the config. 95 | 96 | servertype = eval("socketserver." + config.get("pygopherd", "servertype")) 97 | 98 | class MyServer(servertype): 99 | allow_reuse_address = 1 100 | 101 | def server_bind(self): 102 | """Override server_bind to store server name.""" 103 | servertype.server_bind(self) 104 | 105 | # Set a timeout. 106 | if config.has_option('pygopherd', 'timeout'): 107 | mytimeout = struct.pack("ll", int(config.get('pygopherd', 'timeout')), 0) 108 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, 109 | mytimeout) 110 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, 111 | mytimeout) 112 | #self.socket.settimeout(int(config.get('pygopherd', 'timeout'))) 113 | host, port = self.socket.getsockname() 114 | if config.has_option("pygopherd", "servername"): 115 | self.server_name = config.get("pygopherd", "servername") 116 | else: 117 | self.server_name = socket.getfqdn(host) 118 | if config.has_option("pygopherd", "advertisedport"): 119 | self.server_port = config.getint("pygopherd", "advertisedport") 120 | else: 121 | self.server_port = port 122 | 123 | # Instantiate a server. Has to be done before the security so we can 124 | # get a privileged port if necessary. 125 | 126 | interface = '' 127 | if config.has_option('pygopherd', 'interface'): 128 | servername = config.get('pygopherd', 'interface') 129 | interface = config.get('pygopherd', 'interface') 130 | 131 | try: 132 | s = MyServer((interface, config.getint('pygopherd', 'port')), 133 | GopherRequestHandler) 134 | except: 135 | GopherExceptions.log(sys.exc_info()[1], None, None) 136 | logger.log("Application startup NOT successful!") 137 | raise 138 | 139 | s.config = config 140 | return s 141 | 142 | def initsecurity(config): 143 | idsetuid = None 144 | idsetgid = None 145 | 146 | if config.has_option("pygopherd", "setuid"): 147 | import pwd 148 | idsetuid = pwd.getpwnam(config.get("pygopherd", "setuid"))[2] 149 | 150 | if config.has_option("pygopherd", "setgid"): 151 | import grp 152 | idsetgid = grp.getgrnam(config.get("pygopherd", "setgid"))[2] 153 | 154 | if config.getboolean("pygopherd", "usechroot"): 155 | os.chroot(config.get("pygopherd", "root")) 156 | logger.log("Chrooted to " + config.get("pygopherd", "root")) 157 | config.set("pygopherd", "root", "/") 158 | 159 | if idsetuid != None or idsetgid != None: 160 | os.setgroups( () ) 161 | logger.log("Supplemental group list cleared.") 162 | 163 | if idsetgid != None: 164 | os.setregid(idsetgid, idsetgid) 165 | logger.log("Switched to group %d" % idsetgid) 166 | 167 | if idsetuid != None: 168 | os.setreuid(idsetuid, idsetuid) 169 | logger.log("Switched to uid %d" % idsetuid) 170 | 171 | def initconditionaldetach(config): 172 | if config.getboolean("pygopherd", "detach"): 173 | pid = os.fork() 174 | if pid: 175 | logger.log("Parent process detaching; child is %d" % pid) 176 | sys.exit(0) 177 | 178 | def initpidfile(config): 179 | if config.has_option("pygopherd", "pidfile"): 180 | pidfile = config.get("pygopherd", "pidfile") 181 | fd = open(pidfile, "wt") 182 | fd.write("%d\n" % os.getpid()) 183 | fd.close() 184 | 185 | def initpgrp(config): 186 | if 'setpgrp' in os.__dict__: 187 | os.setpgrp() 188 | pgrp = os.getpgrp() 189 | logger.log("Process group is %d" % pgrp) 190 | return pgrp 191 | else: 192 | logger.log("setpgrp() unavailable; not initializing process group") 193 | return None 194 | 195 | def initsighandlers(config, pgrp): 196 | sighandlers.setsighuphandler() 197 | sighandlers.setsigtermhandler(pgrp) 198 | 199 | def initeverything(conffile): 200 | config = initconffile(conffile) 201 | initlogger(config, conffile) 202 | initexceptions(config) 203 | initmimetypes(config) 204 | s = getserverobject(config) 205 | initconditionaldetach(config) 206 | initpidfile(config) 207 | pgrp = initpgrp(config) 208 | initsighandlers(config, pgrp) 209 | initsecurity(config) 210 | os.chdir(config.get("pygopherd", "root")) 211 | 212 | logger.log("Running. Root is '%s'" % config.get("pygopherd", "root")) 213 | return s 214 | 215 | -------------------------------------------------------------------------------- /pygopherd/initializationTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Python-based gopher server 4 | # Module: test of initialization code 5 | # COPYRIGHT # 6 | # Copyright (C) 2002 John Goerzen 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; version 2 of the License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # END OF COPYRIGHT # 21 | 22 | import unittest, socketserver, mimetypes 23 | from pygopherd import initialization 24 | 25 | from pygopherd import logger, fileext 26 | 27 | 28 | class initializationConfigTestCase(unittest.TestCase): 29 | def testinitconffile(self): 30 | self.assertRaises(Exception, initialization.initconffile, 31 | '/foo') 32 | # Load the standard config file. It should be OK at least. 33 | config = initialization.initconffile('conf/pygopherd.conf') 34 | assert not config.has_option("pygopherd", "servername"), \ 35 | "servername should be disabled by default" 36 | self.assertEqual(config.getint("pygopherd", "port"), 70, 37 | "Port should be 70") 38 | self.assertEqual(config.get("pygopherd", "servertype"), 39 | "ForkingTCPServer", 40 | "Servertype should be ForkingTCPServer") 41 | assert config.getboolean("pygopherd", "tracebacks"), \ 42 | "Tracebacks should be enabled." 43 | 44 | class initializationGeneralTestCase(unittest.TestCase): 45 | def setUp(self): 46 | self.config = initialization.initconffile('conf/pygopherd.conf') 47 | 48 | def testinitlogger(self): 49 | # FIXME Can't really test this. 50 | pass 51 | 52 | def testinitmimetypes_except(self): 53 | self.config.set("pygopherd", "mimetypes", "/foo:/bar") 54 | self.assertRaises(Exception, initialization.initmimetypes, 55 | self.config) 56 | 57 | def testinitmimetypes(self): 58 | # Logger is required for this test. 59 | self.config.set("logger", "logmethod", "none") 60 | initialization.initlogger(self.config, 'TESTING') 61 | initialization.initmimetypes(self.config) 62 | self.assertEqual(mimetypes.types_map['.txt'], 'text/plain') 63 | self.assertEqual(mimetypes.encodings_map['.bz2'], 'bzip2') 64 | assert '.txt' in fileext.typemap['text/plain'] 65 | 66 | def testgetserverobject(self): 67 | self.config.set("pygopherd", "port", "22270") 68 | s = initialization.getserverobject(self.config) 69 | assert isinstance(s, socketserver.ForkingTCPServer) 70 | 71 | def testinitsecurity(self): 72 | #FIXME 73 | pass 74 | 75 | -------------------------------------------------------------------------------- /pygopherd/logger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | log = None 4 | logfile = sys.stdout 5 | priority = None 6 | facility = None 7 | syslogfunc = None 8 | 9 | 10 | def log_file(message): 11 | global logfile 12 | logfile.write(message + "\n") 13 | 14 | def setlogfile(file): 15 | global logfile 16 | logfile = file 17 | 18 | def log_syslog(message): 19 | syslogfunc(priority, message) 20 | 21 | def log_none(message): 22 | pass 23 | 24 | def init(config): 25 | global log, priority, facility, syslogfunc 26 | logmethod = config.get("logger", "logmethod") 27 | if logmethod == "syslog": 28 | import syslog 29 | priority = eval("syslog." + config.get("logger", "priority")) 30 | facility = eval("syslog." + config.get("logger", "facility")) 31 | syslog.openlog('pygopherd', syslog.LOG_PID, facility) 32 | syslogfunc = syslog.syslog 33 | log = log_syslog 34 | elif logmethod == 'file': 35 | log = log_file 36 | else: 37 | log = log_none 38 | 39 | -------------------------------------------------------------------------------- /pygopherd/loggerTest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pygopherd import logger, testutil 3 | 4 | class LoggerTestCase(unittest.TestCase): 5 | def setUp(self): 6 | self.config = testutil.getconfig() 7 | 8 | 9 | -------------------------------------------------------------------------------- /pygopherd/pipe.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: Execute children in a pipe. 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import os, sys 20 | 21 | # Later we will check sys.platform 22 | 23 | def pipedata_unix(file, args, environ = os.environ, 24 | childstdin = None, 25 | childstdout = None, 26 | childstderr = None, 27 | pathsearch = 0): 28 | pid = os.fork() 29 | if pid: 30 | # Parent. 31 | return os.waitpid(pid, 0)[1] 32 | else: 33 | # Child. 34 | if childstdin: 35 | os.dup2(childstdin.fileno(), 0) 36 | if childstdout: 37 | os.dup2(childstdout.fileno(), 1) 38 | if childstderr: 39 | os.dup2(childstderr.fileno(), 2) 40 | if pathsearch: 41 | os.execvpe(file, args, environ) 42 | else: 43 | os.execve(file, args, environ) 44 | sys.exit(255) 45 | 46 | pipedata = pipedata_unix 47 | -------------------------------------------------------------------------------- /pygopherd/pipeTest.py: -------------------------------------------------------------------------------- 1 | import unittest, os 2 | from pygopherd import pipe, testutil 3 | 4 | class PipeTestCase(unittest.TestCase): 5 | def setUp(self): 6 | self.config = testutil.getconfig() 7 | self.root = self.config.get("pygopherd", "root") 8 | self.testdata = self.root + "/pygopherd/pipetestdata" 9 | self.testprog = self.root + "/pygopherd/pipetest.sh" 10 | 11 | def testWorkingPipe(self): 12 | outputfd = os.tmpfile() 13 | inputfd = open(self.testdata, "rt") 14 | 15 | retval = pipe.pipedata(self.testprog, [self.testprog], 16 | childstdin = inputfd, 17 | childstdout = outputfd) 18 | outputfd.seek(0) 19 | 20 | self.assertEqual(outputfd.read(), 21 | "Starting\nGot [Word1]\nGot [Word2]\nGot [Word3]\nEnding\n") 22 | self.assertTrue(os.WIFEXITED(retval), "WIFEXITED was not true") 23 | self.assertEqual(os.WEXITSTATUS(retval), 0) 24 | self.assertEqual(retval, 0) 25 | outputfd.close() 26 | 27 | def testFailingPipe(self): 28 | outputfd = os.tmpfile() 29 | 30 | -------------------------------------------------------------------------------- /pygopherd/protocols/ProtocolMultiplexer.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: find the right protocol to handle a request 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | from pygopherd import handlers, protocols 20 | from pygopherd.protocols import * 21 | from pygopherd.GopherExceptions import FileNotFound 22 | import re 23 | 24 | def getProtocol(request, server, requesthandler, rfile, wfile, config): 25 | p = eval(config.get("protocols.ProtocolMultiplexer", "protocols")) 26 | 27 | for protocol in p: 28 | ptry = protocol(request, server, requesthandler, rfile, wfile, config) 29 | if ptry.canhandlerequest(): 30 | return ptry 31 | -------------------------------------------------------------------------------- /pygopherd/protocols/ProtocolMultiplexerTest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from io import StringIO 3 | from pygopherd.protocols import ProtocolMultiplexer 4 | from pygopherd import testutil 5 | import pygopherd.protocols 6 | 7 | class ProtocolMultiplexerTestCase(unittest.TestCase): 8 | def setUp(self): 9 | self.config = testutil.getconfig() 10 | 11 | # Just a bunch of test cases for each different protocol -- make 12 | # sure we find the right one. 13 | 14 | def getproto(self, request): 15 | rfile = StringIO(request) 16 | wfile = StringIO() 17 | handler = testutil.gettestinghandler(rfile, wfile, 18 | self.config) 19 | return ProtocolMultiplexer.getProtocol(file.readline(), 20 | handler.server, 21 | handler, 22 | handler.rfile, 23 | handler.wfile, 24 | self.config) 25 | 26 | def testGoToGopher(self): 27 | assert isinstance(testutil.gettestingprotocol("/gopher0-request.txt\n"), pygopherd.protocols.rfc1436.GopherProtocol) 28 | 29 | def testGoToHTTP(self): 30 | assert isinstance(testutil.gettestingprotocol("GET /http-request.txt HTTP/1.0\n\n"), 31 | pygopherd.protocols.http.HTTPProtocol) 32 | 33 | def testGoToGopherPlus(self): 34 | assert isinstance(testutil.gettestingprotocol("/gopher+-request.txt\t+\n"), 35 | pygopherd.protocols.gopherp.GopherPlusProtocol) 36 | 37 | -------------------------------------------------------------------------------- /pygopherd/protocols/__init__.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: protocol directory index 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | __all__ = ['base', 'enhanced', 'gopherp', 'rfc1436', 'http', 'wap'] 20 | -------------------------------------------------------------------------------- /pygopherd/protocols/base.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: base protocol implementation 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import socketserver 20 | import re 21 | import os, stat, os.path, mimetypes 22 | from pygopherd import handlers, GopherExceptions, logger, gopherentry 23 | from pygopherd.handlers import HandlerMultiplexer 24 | 25 | class BaseGopherProtocol: 26 | """Skeleton protocol -- includes commonly-used routines.""" 27 | def __init__(self, request, server, requesthandler, rfile, wfile, config): 28 | """Parameters are: 29 | request -- the raw request string. 30 | 31 | server -- a SocketServer object. 32 | 33 | rfile -- input file. The first line will already have been read. 34 | 35 | wfile -- output file. Where the output should be sent. 36 | 37 | config -- a ConfigParser object.""" 38 | 39 | self.request = request 40 | requestparts = [arg.strip() for arg in request.split("\t")] 41 | self.rfile = rfile 42 | self.wfile = wfile 43 | self.config = config 44 | self.server = server 45 | self.requesthandler = requesthandler 46 | self.requestlist = requestparts 47 | self.searchrequest = None 48 | self.handler = None 49 | 50 | selector = requestparts[0] 51 | selector = self.slashnormalize(selector) 52 | 53 | self.selector = selector 54 | 55 | def slashnormalize(self, selector): 56 | """Normalize slashes in the selector. Make sure it starts 57 | with a slash and does not end with one. If it is a root directory 58 | request, make sure it is exactly '/'. Returns result.""" 59 | if len(selector) and selector[-1] == '/': 60 | selector = selector[0:-1] 61 | if len(selector) == 0 or selector[0] != '/': 62 | selector = '/' + selector 63 | return selector 64 | 65 | 66 | def canhandlerequest(self): 67 | """Decides whether or not a given request is valid for this 68 | protocol. Should be overridden by all subclasses.""" 69 | return 0 70 | 71 | def log(self, handler): 72 | """Log a handled request.""" 73 | logger.log("%s [%s/%s]: %s" % \ 74 | (self.requesthandler.client_address[0], 75 | type(self).__name__, 76 | type(handler).__name__, 77 | self.selector)) 78 | 79 | def handle(self): 80 | """Handles the request.""" 81 | try: 82 | handler = self.gethandler() 83 | self.log(handler) 84 | self.entry = handler.getentry() 85 | handler.prepare() 86 | if handler.isdir(): 87 | self.writedir(self.entry, handler.getdirlist()) 88 | else: 89 | handler.write(self.wfile) 90 | except GopherExceptions.FileNotFound as e: 91 | self.filenotfound(str(e)) 92 | except IOError as e: 93 | GopherExceptions.log(e, self, None) 94 | self.filenotfound(e[1]) 95 | 96 | def filenotfound(self, msg): 97 | self.wfile.write(b"3%s\t\terror.host\t1\r\n" % msg.encode(encoding='cp437')) 98 | 99 | def gethandler(self): 100 | """Gets the handler for this object's selector.""" 101 | if not self.handler: 102 | self.handler = HandlerMultiplexer.getHandler(self.selector, self.searchrequest, 103 | self, self.config) 104 | return self.handler 105 | 106 | def writedir(self, entry, dirlist): 107 | """Called to render a directory. Generally called by self.handle()""" 108 | 109 | startstr = self.renderdirstart(entry) 110 | if startstr != None: 111 | self.wfile.write(startstr) 112 | 113 | abstractopt = self.config.get("pygopherd", "abstract_entries") 114 | doabstracts = abstractopt == 'always' or \ 115 | (abstractopt == 'unsupported' and 116 | not self.groksabstract()) 117 | 118 | if self.config.getboolean("pygopherd", "abstract_headers"): 119 | self.wfile.write(self.renderabstract(entry.getea('ABSTRACT', ''))) 120 | 121 | for direntry in dirlist: 122 | self.wfile.write(self.renderobjinfo(direntry).encode(encoding='cp437')) 123 | if doabstracts: 124 | abstract = self.renderabstract(direntry.getea('ABSTRACT')) 125 | if abstract: 126 | self.wfile.write(abstract) 127 | 128 | endstr = self.renderdirend(entry) 129 | if endstr != None: 130 | self.wfile.write(endstr) 131 | 132 | def renderabstract(self, abstractstring): 133 | if not abstractstring: 134 | return '' 135 | retval = '' 136 | for line in abstractstring.splitlines(): 137 | absentry = gopherentry.getinfoentry(line, self.config) 138 | retval += self.renderobjinfo(absentry) 139 | return retval 140 | 141 | def renderdirstart(self, entry): 142 | """Renders the start of a directory. Most protocols will not need 143 | this. Exception might be HTML. Returns None if not needed. 144 | Argument should be the entry corresponding to the dir itself.""" 145 | return None 146 | 147 | def renderdirend(self, entry): 148 | """Likewise for the end of a directory.""" 149 | return None 150 | 151 | def renderobjinfo(self, entry): 152 | """Renders an object's info according to the protocol. Returns 153 | a string. A gopher0 server, for instance, would return a dir line. 154 | MUST BE OVERRIDDEN.""" 155 | return None 156 | 157 | def groksabstract(self): 158 | """Returns true if this protocol understands abstracts natively; 159 | false otherwise.""" 160 | return 0 161 | 162 | -------------------------------------------------------------------------------- /pygopherd/protocols/baseTest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pygopherd.handlers.file 3 | from pygopherd import testutil 4 | from io import BytesIO 5 | from pygopherd.protocols.base import BaseGopherProtocol 6 | 7 | class BaseProtocolTestCase(unittest.TestCase): 8 | def setUp(self): 9 | self.config = testutil.getconfig() 10 | self.rfile = BytesIO(b"/testfile.txt\n") 11 | self.wfile = BytesIO() 12 | self.logfile = testutil.getstringlogger() 13 | self.logstr = "10.77.77.77 [BaseGopherProtocol/FileHandler]: /testfile.txt\n" 14 | self.handler = testutil.gettestinghandler(self.rfile, self.wfile, 15 | self.config) 16 | self.server = self.handler.server 17 | self.proto = BaseGopherProtocol("/testfile.txt\n", self.server, self.handler, 18 | self.rfile, self.wfile, self.config) 19 | 20 | def testInitBasic(self): 21 | proto = BaseGopherProtocol("/foo.txt\n", self.server, self.handler, 22 | self.rfile, self.wfile, self.config) 23 | assert proto.rfile == self.rfile 24 | assert proto.wfile == self.wfile 25 | assert proto.config == self.config 26 | assert proto.requesthandler == self.handler 27 | assert proto.requestlist == ['/foo.txt'] 28 | assert proto.searchrequest == None 29 | assert proto.handler == None 30 | assert proto.selector == '/foo.txt' 31 | 32 | def testInitEmpty(self): 33 | proto = BaseGopherProtocol("\n", self.server, self.handler, 34 | self.rfile, self.wfile, self.config) 35 | # It should be rewritten to / 36 | assert proto.selector == '/' 37 | assert proto.requestlist == [''] 38 | 39 | def testInitMissingLeadingSlash(self): 40 | proto = BaseGopherProtocol("foo.txt\n", self.server, self.handler, 41 | self.rfile, self.wfile, self.config) 42 | assert proto.selector == '/foo.txt' 43 | assert proto.requestlist == ['foo.txt'] 44 | 45 | def testInitAddedTrailingSlash(self): 46 | proto = BaseGopherProtocol("/dir/\n", self.server, self.handler, 47 | self.rfile, self.wfile, self.config) 48 | assert proto.selector == '/dir' 49 | assert proto.requestlist == ['/dir/'] 50 | 51 | def testInitBothSlashProblems(self): 52 | proto = BaseGopherProtocol("dir/\n", self.server, self.handler, 53 | self.rfile, self.wfile, self.config) 54 | assert proto.selector == '/dir' 55 | assert proto.requestlist == ['dir/'] 56 | 57 | def testInitSplit(self): 58 | proto = BaseGopherProtocol("foo.txt\t+\n", self.server, self.handler, 59 | self.rfile, self.wfile, self.config) 60 | assert proto.selector == '/foo.txt' 61 | assert proto.requestlist == ['foo.txt', '+'] 62 | 63 | def testcanhandlerequest(self): 64 | assert not self.proto.canhandlerequest() 65 | 66 | def testlog(self): 67 | # This is a file handler, not a request handler! 68 | handler = self.proto.gethandler() 69 | self.proto.log(handler) 70 | self.assertEqual(self.logfile.getvalue(), self.logstr) 71 | 72 | def testhandle_file(self): 73 | self.proto.handle() 74 | self.assertEqual(self.logfile.getvalue(), self.logstr) 75 | self.assertEqual(self.wfile.getvalue(), b"Test\n") 76 | 77 | def testhandle_notfound(self): 78 | proto = BaseGopherProtocol("/NONEXISTANT.txt\n", self.server, 79 | self.handler, self.rfile, self.wfile, 80 | self.config) 81 | proto.handle() 82 | self.assertEqual(self.logfile.getvalue(), 83 | "10.77.77.77 [BaseGopherProtocol/None] EXCEPTION FileNotFound: '/NONEXISTANT.txt' does not exist (no handler found)\n") 84 | self.assertEqual(self.wfile.getvalue(), b"3'/NONEXISTANT.txt' does not exist (no handler found)\t\terror.host\t1\r\n") 85 | 86 | # We cannot test handle_dir here because we don't have enough info. 87 | 88 | def testfilenotfound(self): 89 | self.proto.filenotfound(b"FOO") 90 | self.assertEqual(self.wfile.getvalue(), 91 | b"3FOO\t\terror.host\t1\r\n") 92 | 93 | def testgethandler(self): 94 | handler = self.proto.gethandler() 95 | assert isinstance(handler, pygopherd.handlers.file.FileHandler) 96 | # Make sure caching works. 97 | assert handler == self.proto.gethandler() 98 | 99 | ## CANNOT TEST: writedir, renderabstract 100 | 101 | def testrenderdirstart(self): 102 | assert self.proto.renderdirstart('foo') == None 103 | 104 | def testrenderdirend(self): 105 | assert self.proto.renderdirend('foo') == None 106 | 107 | def testrenderobjinfo(self): 108 | assert self.proto.renderobjinfo('foo') == None 109 | 110 | def testgroksabstract(self): 111 | assert not self.proto.groksabstract() 112 | -------------------------------------------------------------------------------- /pygopherd/protocols/enhanced.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: experimental enhanced gopher protocol 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import socketserver 20 | import re 21 | import os, stat, os.path, mimetypes 22 | from pygopherd import handlers, protocols 23 | from pygopherd.protocols import rfc1436 24 | 25 | class EnhancedGopherProtocol(rfc1436.GopherProtocol): 26 | def renderobjinfo(self, entry): 27 | return entry.gettype() + \ 28 | entry.getname() + "\t" + \ 29 | entry.getselector() + "\t" + \ 30 | entry.gethost(default = self.server.server_name) + "\t" + \ 31 | str(entry.getport(default = self.server.server_port)) + "\t" + \ 32 | str(entry.getsize()) + "\t" + \ 33 | entry.getmimetype() + "\t" + \ 34 | entry.getencoding() + "\t" + \ 35 | entry.getlanguage() 36 | -------------------------------------------------------------------------------- /pygopherd/protocols/gopherp.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: server entry point 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import socketserver 20 | import re 21 | import os, stat, os.path, mimetypes, time 22 | from pygopherd import handlers, protocols, GopherExceptions 23 | from pygopherd.protocols.rfc1436 import GopherProtocol 24 | 25 | class GopherPlusProtocol(GopherProtocol): 26 | """Implementation of Gopher+ protocol. Will handle Gopher+ 27 | queries ONLY.""" 28 | 29 | def canhandlerequest(self): 30 | """We can handle the request IF: 31 | * It has more than one parameter in the request list 32 | * The second parameter is ! or starts with + or $""" 33 | if len(self.requestlist) < 2: 34 | return 0 35 | if len(self.requestlist) == 2: 36 | self.gopherpstring = self.requestlist[1] 37 | elif len(self.requestlist) == 3: 38 | self.gopherpstring = self.requestlist[2] 39 | self.searchrequest = self.requestlist[1] 40 | else: 41 | return 0 # Too many params. 42 | 43 | return self.gopherpstring[0] == '+' or \ 44 | self.gopherpstring == '!' or \ 45 | self.gopherpstring[0] == '$' 46 | 47 | def handle(self): 48 | """Handle Gopher+ request.""" 49 | self.handlemethod = None 50 | if self.gopherpstring[0] == '+': 51 | self.handlemethod = 'documentonly' 52 | elif self.gopherpstring == '!': 53 | self.handlemethod = 'infoonly' 54 | elif self.gopherpstring[0] == '$': 55 | self.handlemethod = 'gopherplusdir' 56 | 57 | try: 58 | handler = self.gethandler() 59 | self.log(handler) 60 | self.entry = handler.getentry() 61 | 62 | if self.handlemethod == 'infoonly': 63 | self.wfile.write("+-2\r\n") 64 | self.wfile.write(self.renderobjinfo(self.entry)) 65 | else: 66 | handler.prepare() 67 | self.wfile.write("+" + str(self.entry.getsize(-2)) + "\r\n") 68 | if handler.isdir(): 69 | self.writedir(self.entry, handler.getdirlist()) 70 | else: 71 | handler.write(self.wfile) 72 | except GopherExceptions.FileNotFound as e: 73 | self.filenotfound(str(e)) 74 | except IOError as e: 75 | GopherExceptions.log(e, self, None) 76 | self.filenotfound(e[1]) 77 | 78 | def getsupportedblocknames(self, entry): 79 | # Return the always-supported values PLUS any extra ones for 80 | # this particular entry. 81 | return ['+INFO', '+ADMIN', '+VIEWS'] + \ 82 | ['+' + x for x in list(entry.geteadict().keys())] 83 | 84 | def getallblocks(self, entry): 85 | retstr = '' 86 | for block in self.getsupportedblocknames(entry): 87 | retstr += self.getblock(block, entry) 88 | return retstr 89 | 90 | def getblock(self, block, entry): 91 | # If the entry has the block in its eadict, return that. 92 | # Otherwise, do our own thing. 93 | # Incoming block: +VIEWS 94 | blockname = block[1:].lower() 95 | 96 | if blockname.upper() in entry.geteadict(): 97 | return "+" + blockname.upper() + ":\r\n" + \ 98 | ''.join( 99 | [" " + x + "\r\n" for x in \ 100 | entry.getea(blockname.upper()).splitlines()] 101 | ) 102 | 103 | # Not in there -- look up a custom function. 104 | 105 | # Name: views 106 | funcname = "get" + blockname + "block" 107 | # Funcname: getviewsblock 108 | func = getattr(self, funcname) 109 | return func(entry) 110 | 111 | def getinfoblock(self, entry): 112 | return "+INFO: " + \ 113 | GopherProtocol.renderobjinfo(self, entry) 114 | 115 | def getadminblock(self, entry): 116 | retstr = "+ADMIN:\r\n" 117 | retstr += " Admin: " 118 | retstr += self.config.get("protocols.gopherp.GopherPlusProtocol", 119 | "admin") 120 | retstr += "\r\n" 121 | if entry.getmtime(): 122 | retstr += " Mod-Date: " 123 | retstr += time.ctime(entry.getmtime()) 124 | m = time.localtime(entry.getmtime()) 125 | retstr += " <%04d%02d%02d%02d%02d%02d>\r\n" % \ 126 | (m[0], m[1], m[2], m[3], m[4], m[5]) 127 | return retstr 128 | 129 | def getviewsblock(self, entry): 130 | retstr = '' 131 | if entry.getmimetype(): 132 | retstr += "+VIEWS:\r\n " + entry.getmimetype() 133 | if (entry.getlanguage()): 134 | retstr += " " + entry.getlanguage() 135 | retstr += ":" 136 | if (entry.getsize() != None): 137 | retstr += " <%dk>" % (entry.getsize() / 1024) 138 | retstr += "\r\n" 139 | return retstr 140 | 141 | def renderobjinfo(self, entry): 142 | if entry.getmimetype('FAKE') == 'application/gopher-menu' and \ 143 | entry.getgopherpsupport(): 144 | entry.mimetype = 'application/gopher+-menu' 145 | if self.handlemethod == 'documentonly': 146 | # It's a Gopher+ request for a gopher0 menu entry. 147 | retstr = GopherProtocol.renderobjinfo(self, entry) 148 | return retstr 149 | else: 150 | return self.getallblocks(entry) 151 | 152 | def filenotfound(self, msg): 153 | self.wfile.write("--2\r\n") 154 | self.wfile.write("1 ") 155 | self.wfile.write(self.config.get("protocols.gopherp.GopherPlusProtocol", "admin")) 156 | self.wfile.write("\r\n" + msg + "\r\n") 157 | 158 | def groksabstract(self): 159 | return 1 160 | 161 | class URLGopherPlus(GopherPlusProtocol): 162 | def getsupportedblocknames(self): 163 | return GopherPlusProtocol.getsupportedblocknames(self) + \ 164 | ['+URL'] 165 | 166 | def geturlblock(self, entry): 167 | return "+URL: %s\r\n" % entry.geturl(self.server.server_name, 168 | self.server.server_port) 169 | -------------------------------------------------------------------------------- /pygopherd/protocols/rfc1436.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: implementation of standard gopher (RFC 1436) protocol 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | 19 | import socketserver 20 | import re 21 | import os, stat, os.path, mimetypes 22 | from pygopherd import handlers, protocols 23 | from pygopherd.protocols.base import BaseGopherProtocol 24 | 25 | class GopherProtocol(BaseGopherProtocol): 26 | """Implementation of basic protocol. Will handle every query.""" 27 | def canhandlerequest(self): 28 | if len(self.requestlist) > 1: 29 | self.searchrequest = self.requestlist[1] 30 | return 1 31 | 32 | def renderobjinfo(self, entry): 33 | #print entry.gettype(), entry.getname(), entry.getselector(), \ 34 | # entry.gethost(), entry.getport(), entry.getgopherpsupport() 35 | retval = entry.gettype('0') + \ 36 | entry.getname() + "\t" + \ 37 | entry.getselector() + "\t" + \ 38 | entry.gethost(default = self.server.server_name) + "\t" + \ 39 | str(entry.getport(default = self.server.server_port)) 40 | if entry.getgopherpsupport(): 41 | return retval + "\t+\r\n" 42 | else: 43 | return retval + "\r\n" 44 | 45 | -------------------------------------------------------------------------------- /pygopherd/protocols/rfc1436Test.py: -------------------------------------------------------------------------------- 1 | import unittest, re 2 | from pygopherd.protocols.rfc1436 import GopherProtocol 3 | from pygopherd import testutil 4 | from io import BytesIO 5 | 6 | class RFC1436TestCase(unittest.TestCase): 7 | def setUp(self): 8 | self.config = testutil.getconfig() 9 | self.rfile = BytesIO(b"/testfile.txt\n") 10 | self.wfile = BytesIO() 11 | self.logfile = testutil.getstringlogger() 12 | self.handler = testutil.gettestinghandler(self.rfile, self.wfile, 13 | self.config) 14 | self.server = self.handler.server 15 | self.proto = GopherProtocol("/testfile.txt\n", self.server, 16 | self.handler, self.rfile, self.wfile, 17 | self.config) 18 | 19 | def testcanhandlerequest(self): 20 | assert self.proto.canhandlerequest() 21 | proto = GopherProtocol("/testfile.txt\tsearch\n", 22 | self.server, self.handler, self.rfile, 23 | self.wfile, self.config) 24 | assert proto.canhandlerequest() 25 | self.assertEqual(proto.selector, '/testfile.txt') 26 | self.assertEqual(proto.searchrequest, "search") 27 | 28 | def testrenderobjinfo(self): 29 | expected = "0testfile.txt\t/testfile.txt\t%s\t%d\t+\r\n" % \ 30 | (self.server.server_name, self.server.server_port) 31 | self.assertEqual(self.proto.renderobjinfo(self.proto.gethandler().getentry()), 32 | expected) 33 | 34 | def testhandle_file(self): 35 | self.proto.handle() 36 | self.assertEqual(self.logfile.getvalue(), 37 | "10.77.77.77 [GopherProtocol/FileHandler]: /testfile.txt\n") 38 | self.assertEqual(self.wfile.getvalue(), b"Test\n") 39 | 40 | def testhandle_file_zipped(self): 41 | self.config.set("handlers.ZIP.ZIPHandler", "enabled", 'true') 42 | from pygopherd.handlers import HandlerMultiplexer 43 | HandlerMultiplexer.handlers = None 44 | handlerlist = self.config.get("handlers.HandlerMultiplexer", "handlers") 45 | handlerlist = handlerlist.strip() 46 | handlerlist = handlerlist[0] + 'ZIP.ZIPHandler, ' + handlerlist[1:] 47 | self.config.set("handlers.HandlerMultiplexer", "handlers", handlerlist) 48 | self.proto = GopherProtocol("/testdata.zip/pygopherd/ziponly\n", 49 | self.server, 50 | self.handler, self.rfile, self.wfile, 51 | self.config) 52 | self.proto.handle() 53 | self.assertEqual(self.wfile.getvalue(), b"ZIPonly\n") 54 | self.config.set("handlers.ZIP.ZIPHandler", "enabled", "false") 55 | 56 | def testhandle_dir_abstracts(self): 57 | proto = GopherProtocol("", self.server, self.handler, self.rfile, 58 | self.wfile, self.config) 59 | proto.handle() 60 | self.assertEqual(proto.selector, '/') 61 | self.assertEqual(self.logfile.getvalue(), 62 | "10.77.77.77 [GopherProtocol/UMNDirHandler]: /\n") 63 | # Try to make this easy on us to fix. 64 | actualarr = self.wfile.getvalue().splitlines() 65 | expectedarr = [ 66 | 'iThis is the abstract for the testdata directory.\tfake\t(NULL)\t0', 67 | '0README\t/README\tHOSTNAME\t64777\t+', 68 | '1pygopherd\t/pygopherd\tHOSTNAME\t64777\t+', 69 | '9symlinktest\t/symlinktest.zip\tHOSTNAME\t64777\t+', 70 | '9testarchive\t/testarchive.tar\tHOSTNAME\t64777\t+', 71 | '9testarchive.tar.gz\t/testarchive.tar.gz\tHOSTNAME\t64777\t+', 72 | '9testarchive.tgz\t/testarchive.tgz\tHOSTNAME\t64777\t+', 73 | '9testdata\t/testdata.zip\tHOSTNAME\t64777\t+', 74 | '9testdata2\t/testdata2.zip\tHOSTNAME\t64777\t+', 75 | '0testfile\t/testfile.txt\tHOSTNAME\t64777\t+', 76 | '9testfile.txt.gz\t/testfile.txt.gz\tHOSTNAME\t64777\t+', 77 | 'iThis is the abstract\tfake\t(NULL)\t0', 78 | 'ifor testfile.txt.gz\tfake\t(NULL)\t0', 79 | '9ziptorture\t/ziptorture.zip\tHOSTNAME\t64777\t+'] 80 | expectedarr = [re.sub('HOSTNAME', self.server.server_name, x) for \ 81 | x in expectedarr] 82 | self.assertEqual(len(actualarr), len(expectedarr), str(actualarr)) 83 | for i in range(len(actualarr)): 84 | self.assertEqual(actualarr[i], expectedarr[i]) 85 | # Make sure proper line endings are present. 86 | self.assertEqual("\r\n".join(actualarr) + "\r\n", self.wfile.getvalue()) 87 | 88 | def testhandle_dir_noabstract(self): 89 | self.config.set("pygopherd", "abstract_headers", "off") 90 | self.config.set("pygopherd", "abstract_entries", "off") 91 | proto = GopherProtocol("", self.server, self.handler, self.rfile, 92 | self.wfile, self.config) 93 | proto.handle() 94 | actualarr = self.wfile.getvalue().splitlines() 95 | expectedarr = \ 96 | [b'0README\t/README\tHOSTNAME\t64777\t+', 97 | b'1pygopherd\t/pygopherd\tHOSTNAME\t64777\t+', 98 | b'9symlinktest\t/symlinktest.zip\tHOSTNAME\t64777\t+', 99 | b'9testarchive\t/testarchive.tar\tHOSTNAME\t64777\t+', 100 | b'9testarchive.tar.gz\t/testarchive.tar.gz\tHOSTNAME\t64777\t+', 101 | b'9testarchive.tgz\t/testarchive.tgz\tHOSTNAME\t64777\t+', 102 | b'9testdata\t/testdata.zip\tHOSTNAME\t64777\t+', 103 | b'9testdata2\t/testdata2.zip\tHOSTNAME\t64777\t+', 104 | b'0testfile\t/testfile.txt\tHOSTNAME\t64777\t+', 105 | b'9testfile.txt.gz\t/testfile.txt.gz\tHOSTNAME\t64777\t+', 106 | b'9ziptorture\t/ziptorture.zip\tHOSTNAME\t64777\t+'] 107 | expectedarr = [re.sub(b'HOSTNAME', self.server.server_name.encode(encoding='cp437'), x) for \ 108 | x in expectedarr] 109 | self.assertEqual(len(actualarr), len(expectedarr)) 110 | for i in range(len(actualarr)): 111 | self.assertEqual(actualarr[i], expectedarr[i]) 112 | 113 | -------------------------------------------------------------------------------- /pygopherd/protocols/wap.py: -------------------------------------------------------------------------------- 1 | # pygopherd -- Gopher-based protocol server in Python 2 | # module: serve up gopherspace via wap 3 | # $Id: http.py,v 1.21 2002/04/26 15:18:10 jgoerzen Exp $ 4 | # Copyright (C) 2003 John Goerzen 5 | # 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; version 2 of the License. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | 20 | from .http import HTTPProtocol 21 | from io import StringIO 22 | import cgi, re 23 | 24 | accesskeys = '1234567890#*' 25 | wmlheader = """ 26 | 28 | 29 | """ 30 | 31 | class WAPProtocol(HTTPProtocol): 32 | # canhandlerequest inherited 33 | def canhandlerequest(self): 34 | ishttp = HTTPProtocol.canhandlerequest(self) 35 | if not ishttp: 36 | return 0 37 | 38 | waptop = self.config.get("protocols.wap.WAPProtocol", 39 | "waptop") 40 | self.waptop = waptop 41 | if self.requestparts[1].startswith(waptop): 42 | # If it starts with waptop, *guaranteed* to be wap. 43 | self.requestparts[1] = self.requestparts[1][len(waptop):] 44 | return 1 45 | 46 | self.headerslurp() 47 | 48 | # See if we can auto-detect a WAP browser. 49 | if 'accept' not in self.httpheaders: 50 | return 0 51 | 52 | if not re.search('[, ]text/vnd.wap.wml', self.httpheaders['accept']): 53 | return 0 54 | 55 | # By now, we know that it lists WML in accept. Let's try a few 56 | # more things. 57 | 58 | for tryitem in ['x-wap-profile', 'x-up-devcap-max-pdu']: 59 | if tryitem in self.httpheaders: 60 | return 1 61 | 62 | return 0 63 | 64 | def adjustmimetype(self, mimetype): 65 | self.needsconversion = 0 66 | if mimetype == None or mimetype == 'text/plain': 67 | self.needsconversion = 1 68 | return 'text/vnd.wap.wml' 69 | if mimetype == 'application/gopher-menu': 70 | return 'text/vnd.wap.wml' 71 | return mimetype 72 | 73 | def getrenderstr(self, entry, url): 74 | global accesskeys 75 | if url.startswith('/'): 76 | url = self.waptop + url 77 | retstr = '' 78 | if not entry.gettype() in ['i', '7']: 79 | if self.accesskeyidx < len(accesskeys): 80 | retstr += '%s ' % \ 81 | (accesskeys[self.accesskeyidx], 82 | accesskeys[self.accesskeyidx], 83 | url) 84 | self.accesskeyidx += 1 85 | else: 86 | retstr += '' % url 87 | if entry.getname() != None: 88 | thisname = cgi.escape(entry.getname()) 89 | else: 90 | thisname = cgi.escape(entry.getselector()) 91 | retstr += thisname 92 | if not entry.gettype() in ['i', '7']: 93 | retstr += '' 94 | if entry.gettype() == '7': 95 | retstr += '
\n' 96 | retstr += ' \n' % \ 97 | self.postfieldidx 98 | retstr += 'Go\n' 99 | #retstr += '\n' 100 | retstr += ' \n' % url#.replace('%', '%25') 101 | retstr += ' \n' % \ 102 | self.postfieldidx 103 | #retstr += ' \n' 104 | retstr += ' \n' 105 | #retstr += '\n' 106 | retstr += '\n' 107 | retstr += "
\n" 108 | self.postfieldidx += 1 109 | return retstr 110 | 111 | def renderdirstart(self, entry): 112 | global wmlheader 113 | self.accesskeyidx = 0 114 | self.postfieldidx = 0 115 | retval = wmlheader 116 | title = 'Gopher' 117 | if self.entry.getname(): 118 | title = cgi.escape(self.entry.getname()) 119 | retval += '' % \ 120 | cgi.escape(title) 121 | 122 | retval += "\n

\n" 123 | retval += "%s
\n" % cgi.escape(title) 124 | return retval 125 | 126 | def renderdirend(self, entry): 127 | return "

\n\n\n" 128 | 129 | def handlerwrite(self, wfile): 130 | global wmlheader 131 | if not self.needsconversion: 132 | self.handler.write(wfile) 133 | return 134 | fakefile = StringIO() 135 | self.handler.write(fakefile) 136 | fakefile.seek(0) 137 | wfile.write(wmlheader) 138 | wfile.write('\n') 139 | wfile.write('

\n') 140 | while 1: 141 | line = fakefile.readline() 142 | if not len(line): 143 | break 144 | line = line.rstrip() 145 | if len(line): 146 | wfile.write(cgi.escape(line) + "\n") 147 | else: 148 | wfile.write("

\n

") 149 | wfile.write('

\n
\n\n') 150 | 151 | def filenotfound(self, msg): 152 | wfile = self.wfile 153 | wfile.write("HTTP/1.0 200 Not Found\r\n") 154 | wfile.write("Content-Type: text/vnd.wap.wml\r\n\r\n") 155 | wfile.write(wmlheader) 156 | wfile.write('\n') 157 | wfile.write('

Gopher Error

\n') 158 | wfile.write(cgi.escape(msg)+ "\n") 159 | wfile.write('

\n
\n\n') 160 | 161 | -------------------------------------------------------------------------------- /pygopherd/sighandlers.py: -------------------------------------------------------------------------------- 1 | # Python-based gopher server 2 | # Module: signal handlers 3 | # COPYRIGHT # 4 | # Copyright (C) 2002 John Goerzen 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; version 2 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | # END OF COPYRIGHT # 19 | 20 | import signal, sys, os 21 | from pygopherd import logger 22 | 23 | pgrp = None 24 | pid = None 25 | 26 | def huphandler(signum, frame): 27 | logger.log("SIGHUP (%d) received; terminating process" % signum) 28 | os._exit(5) # So we don't raise SystemExit 29 | 30 | def termhandler(signum, frame): 31 | if os.getpid() == pid: # Master killed; kill children. 32 | logger.log("SIGTERM (%d) received in master; doing orderly shutdown" \ 33 | % signum) 34 | logger.log("Terminating all of process group %d with SIGHUP" % \ 35 | (pgrp)) 36 | # Ignore this signal so that our own process won't get it again. 37 | signal.signal(signal.SIGHUP, signal.SIG_IGN) 38 | os.kill(0, signal.SIGHUP) 39 | logger.log("Master application process now exiting. Goodbye.") 40 | sys.exit(6) 41 | else: # Shouldn't need this -- just in case. 42 | logger.log("SIGTERM (%d) received in child; terminating this process" \ 43 | % signum) 44 | os._exit(7) # So we don't raise SystemExit 45 | 46 | def setsighuphandler(): 47 | if 'SIGHUP' in signal.__dict__: 48 | signal.signal(signal.SIGHUP, huphandler) 49 | 50 | def setsigtermhandler(newpgrp): 51 | global pgrp, pid 52 | pgrp = newpgrp 53 | pid = os.getpid() 54 | signal.signal(signal.SIGTERM, termhandler) 55 | 56 | 57 | -------------------------------------------------------------------------------- /pygopherd/testutil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Python-based gopher server 4 | # Module: test utilities 5 | # COPYRIGHT # 6 | # Copyright (C) 2002 John Goerzen 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; version 2 of the License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # END OF COPYRIGHT # 21 | 22 | 23 | from pygopherd import initialization, logger 24 | from pygopherd.protocols import ProtocolMultiplexer 25 | from io import StringIO 26 | import os 27 | 28 | def getconfig(): 29 | config = initialization.initconffile('conf/pygopherd.conf') 30 | config.set("pygopherd", "root", os.path.abspath('./testdata')) 31 | return config 32 | 33 | def getstringlogger(): 34 | config = getconfig() 35 | config.set('logger', 'logmethod', 'file') 36 | logger.init(config) 37 | stringfile = StringIO() 38 | logger.setlogfile(stringfile) 39 | return stringfile 40 | 41 | def gettestingserver(config = None): 42 | config = config or getconfig() 43 | config.set('pygopherd', 'port', '64777') 44 | s = initialization.getserverobject(config) 45 | s.server_close() 46 | return s 47 | 48 | def gettestinghandler(rfile, wfile, config = None): 49 | """Creates a testing handler with input from rfile. Fills in 50 | other stuff with fake values.""" 51 | 52 | config = config or getconfig() 53 | 54 | # Kludge to pass to the handler init. 55 | 56 | class requestClass: 57 | def __init__(self, rfile, wfile): 58 | self.rfile = rfile 59 | self.wfile = wfile 60 | def makefile(self, mode, bufsize): 61 | if mode[0] == 'r': 62 | return self.rfile 63 | return self.wfile 64 | 65 | class handlerClass(initialization.GopherRequestHandler): 66 | def __init__(self, request, client_address, server): 67 | self.request = request 68 | self.client_address = client_address 69 | self.server = server 70 | self.setup() 71 | 72 | s = gettestingserver(config) 73 | rhandler = handlerClass(requestClass(rfile, wfile), 74 | ('10.77.77.77', '7777'), 75 | s) 76 | return rhandler 77 | 78 | def gettestingprotocol(request, config = None): 79 | config = config or getconfig() 80 | 81 | rfile = StringIO(request) 82 | # Pass fake rfile, wfile to gettestinghandler -- they'll be closed before 83 | # we can get the info, and some protocols need to read more from them. 84 | 85 | handler = gettestinghandler(StringIO(), StringIO(), config) 86 | # Now override. 87 | handler.rfile = rfile 88 | return ProtocolMultiplexer.getProtocol(rfile.readline(), 89 | handler.server, 90 | handler, 91 | handler.rfile, 92 | handler.wfile, 93 | config) 94 | -------------------------------------------------------------------------------- /pygopherd/version.py: -------------------------------------------------------------------------------- 1 | productname = 'PyGopherd' 2 | versionstr = "2.0.18" 3 | 4 | versionlist = versionstr.split(".") 5 | major = versionlist[0] 6 | minor = versionlist[1] 7 | patch = versionlist[2] 8 | copyright = "Copyright (C) 2002-2008 John Goerzen. All rights reserved." 9 | author = "John Goerzen" 10 | author_email = "jgoerzen@complete.org" 11 | description = "Multiprotocol Internet Gopher Information Server" 12 | homepage = "http://www.quux.org/devel/gopher/pygopherd" 13 | homegopher = "gopher://quux.org/1/devel/gopher/pygopherd" 14 | license = """Copyright (C) 2002-2008 John Goerzen 15 | 16 | This program is free software; you can redistribute it and/or modify 17 | it under the terms of the GNU General Public License as published by 18 | the Free Software Foundation; version 2 of the License. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | You should have received a copy of the GNU General Public License 26 | along with this program; if not, write to the Free Software 27 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA""" 28 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Python-based gopher server 3 | # Module: main test runner 4 | # COPYRIGHT # 5 | # Copyright (C) 2002-2019 John Goerzen 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; version 2 of the License. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # END OF COPYRIGHT # 20 | 21 | import sys, profile 22 | # sys.path.insert("-1", "..") 23 | 24 | import unittest 25 | from pygopherd import * 26 | import pygopherd.protocols 27 | import pygopherd.protocols.ProtocolMultiplexerTest 28 | import pygopherd.protocols.baseTest 29 | import pygopherd.protocols.rfc1436Test 30 | import pygopherd.protocols 31 | import pygopherd.handlers.ZIP 32 | 33 | def suite(): 34 | tests = [initializationTest, 35 | GopherExceptionsTest, 36 | fileextTest, 37 | gopherentryTest, 38 | loggerTest, 39 | pipeTest, 40 | pygopherd.protocols.ProtocolMultiplexerTest, 41 | pygopherd.protocols.baseTest, 42 | pygopherd.protocols.rfc1436Test, 43 | pygopherd.handlers.ZIP 44 | ] 45 | suite = unittest.TestSuite() 46 | for module in tests: 47 | suite.addTest(unittest.findTestCases(module)) 48 | return suite 49 | 50 | if __name__ == '__main__': 51 | profile.run("unittest.main(defaultTest='suite')", 'profile.out') 52 | # unittest.main(defaultTest='suite') 53 | 54 | -------------------------------------------------------------------------------- /runtestsgui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # COPYRIGHT # 3 | # Copyright (C) 2002 John Goerzen 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; version 2 of the License. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | # END OF COPYRIGHT # 18 | 19 | import unittestgui 20 | unittestgui.main('runtests.suite') 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Python-based gopher server 4 | # Module: installer 5 | # COPYRIGHT # 6 | # Copyright (C) 2002-2019 John Goerzen 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; version 2 of the License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | # END OF COPYRIGHT # 21 | 22 | 23 | from distutils.core import setup 24 | import pygopherd.version 25 | 26 | setup(name = "pygopherd", 27 | version = pygopherd.version.versionstr, 28 | description = pygopherd.version.description, 29 | author = pygopherd.version.author, 30 | author_email = pygopherd.version.author_email, 31 | url = pygopherd.version.homepage, 32 | packages = ['pygopherd', 'pygopherd.handlers', 'pygopherd.protocols'], 33 | scripts = ['bin/pygopherd'], 34 | data_files = [ ('/etc/pygopherd', ['conf/pygopherd.conf', 35 | 'conf/mime.types'] ) ], 36 | license = pygopherd.version.license 37 | ) 38 | 39 | -------------------------------------------------------------------------------- /testdata/.abstract: -------------------------------------------------------------------------------- 1 | This is the abstract for the testdata directory. -------------------------------------------------------------------------------- /testdata/README: -------------------------------------------------------------------------------- 1 | This directory contains data for the unit tests. 2 | 3 | Some tests are dependant upon the precise length of files; those files are 4 | added with -kb. 5 | 6 | -------------------------------------------------------------------------------- /testdata/pygopherd/pipetest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Simple script to echo back what came in. 3 | 4 | echo "Starting" 5 | read DATALINE 6 | while test -n "$DATALINE" ; do 7 | echo "Got [$DATALINE]" 8 | read DATALINE 9 | done 10 | echo "Ending" 11 | -------------------------------------------------------------------------------- /testdata/pygopherd/pipetestdata: -------------------------------------------------------------------------------- 1 | Word1 2 | Word2 3 | Word3 4 | -------------------------------------------------------------------------------- /testdata/symlinktest.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgoerzen/pygopherd/d412606bae28ce07126e8464033bfd8c04071964/testdata/symlinktest.zip -------------------------------------------------------------------------------- /testdata/testarchive.tar: -------------------------------------------------------------------------------- 1 | testfile.txt0100644000175000017500000000000507455345341013674 0ustar jgoerzenjgoerzenTest 2 | -------------------------------------------------------------------------------- /testdata/testarchive.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgoerzen/pygopherd/d412606bae28ce07126e8464033bfd8c04071964/testdata/testarchive.tar.gz -------------------------------------------------------------------------------- /testdata/testarchive.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgoerzen/pygopherd/d412606bae28ce07126e8464033bfd8c04071964/testdata/testarchive.tgz -------------------------------------------------------------------------------- /testdata/testdata.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgoerzen/pygopherd/d412606bae28ce07126e8464033bfd8c04071964/testdata/testdata.zip -------------------------------------------------------------------------------- /testdata/testdata2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgoerzen/pygopherd/d412606bae28ce07126e8464033bfd8c04071964/testdata/testdata2.zip -------------------------------------------------------------------------------- /testdata/testfile.txt: -------------------------------------------------------------------------------- 1 | Test 2 | -------------------------------------------------------------------------------- /testdata/testfile.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgoerzen/pygopherd/d412606bae28ce07126e8464033bfd8c04071964/testdata/testfile.txt.gz -------------------------------------------------------------------------------- /testdata/testfile.txt.gz.abstract: -------------------------------------------------------------------------------- 1 | This is the abstract 2 | for testfile.txt.gz 3 | -------------------------------------------------------------------------------- /testdata/ziptorture.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgoerzen/pygopherd/d412606bae28ce07126e8464033bfd8c04071964/testdata/ziptorture.zip --------------------------------------------------------------------------------