summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJens Vagelpohl <jens@dataflake.org>2009-06-23 10:12:17 +0000
committerJens Vagelpohl <jens@dataflake.org>2009-06-23 10:12:17 +0000
commitd2f411fc73c0cdf8874c95f7d2171ec9e99997c2 (patch)
tree4834c44284891f4808c4994087743188ef10fb6d
parent9f4c96b722d1da71573eab056a5617e687fe8bbb (diff)
downloadProducts.JRedirector-d2f411fc73c0cdf8874c95f7d2171ec9e99997c2.zip
Products.JRedirector-d2f411fc73c0cdf8874c95f7d2171ec9e99997c2.tar.gz
- copying old package into new location
git-svn-id: file:///svn-public/Products.JRedirector/trunk@1808 835909ba-7c00-0410-bfa4-884f43845301
-rw-r--r--Products/JRedirector/CHANGES.txt116
-rw-r--r--Products/JRedirector/INSTALL.txt16
-rw-r--r--Products/JRedirector/JRedirector.py308
-rw-r--r--Products/JRedirector/LICENSE.txt77
-rw-r--r--Products/JRedirector/README.txt161
-rw-r--r--Products/JRedirector/VERSION.txt1
-rw-r--r--Products/JRedirector/__init__.py24
-rw-r--r--Products/JRedirector/dtml/add.dtml71
-rw-r--r--Products/JRedirector/dtml/log.dtml77
-rw-r--r--Products/JRedirector/dtml/mappings.dtml182
-rw-r--r--Products/JRedirector/dtml/settings.dtml66
-rw-r--r--Products/JRedirector/help/Add.stx52
-rw-r--r--Products/JRedirector/help/Log.stx23
-rw-r--r--Products/JRedirector/help/Mappings.stx43
-rw-r--r--Products/JRedirector/help/Properties.stx31
-rw-r--r--Products/JRedirector/www/jredirector.gifbin0 -> 124 bytes
16 files changed, 1248 insertions, 0 deletions
diff --git a/Products/JRedirector/CHANGES.txt b/Products/JRedirector/CHANGES.txt
new file mode 100644
index 0000000..cccd91f
--- /dev/null
+++ b/Products/JRedirector/CHANGES.txt
@@ -0,0 +1,116 @@
+JRedirector version and change information
+
+ 1.3
+
+ Features added:
+
+ - Allow selection of varying log levels including no
+ logging at all. This should put all those at ease who
+ saw alarming ZODB growth on very busy sites that see
+ a lot of redirections.
+
+ - Added interoperability with virtual hosting. The latest
+ versions of Zope seemed to have changed the values that
+ get inserted into the PATH_INFO REQUEST-variable if a
+ VirtualHostMonster is used. (Tracker item 208)
+
+ 1.2
+
+ Features added:
+
+ - Provide data for the Undo tab entries produced by the
+ JRedirector
+
+ - Change the internal storage of logging data to be more
+ efficient. This change is automatically applied to
+ existing JRedirector instances the first time the log
+ is accessed, either during a redirect or when the
+ administrator looks up the log history in the Zope
+ management interface. This should also fix Tracker
+ issue 197 if it was a real issue (my own testing did
+ not support these conclusions).
+
+
+ 1.1
+
+ This is the "Josef Meile Appreciation Release". Virtually
+ all suggestions for improvements in this release came from
+ Josef.
+
+ Features added:
+
+ - Referrers to faulty URLs are now tracked as well. They
+ are listed on the Log tab.
+
+ - Mappings and Log views have been reorganized a little
+ to be more obvious about what they show and to avoid
+ having to scroll the browser window sideways if a URL
+ is overly long.
+
+ - All displayed URLs are now clickable and will open in a
+ new window. This allows for quickly testing the old and
+ new paths on the Mappings tab as well as the referrers
+ listed on the Log tab.
+
+ - Old paths that are specified with a trailing "/" (slash)
+ characters will have it stripped to prevent matches on
+ directory paths failing because most people just don't
+ use trailing slashes when referring to directories.
+
+
+ 1.0
+
+ Features added:
+
+ - Case-insensitive matching of requested paths to redirects
+ (first suggested by Josef Meile).
+
+ - Ability to clear the current logs from the ZMI (suggested
+ by Josef Meile).
+
+ - Multiple paths can now be redirected to a single new path
+ by specifying a "Wildcard Mapping". Wildcard mappings are
+ used for those requested URLs that...
+
+ - have not matched any other non-wildcard rule
+
+ - are *underneath* the path specified as Old Path when
+ setting up the mapping.
+
+ - Added more allowed HTTP response codes and an explanation
+ of these codes in README.txt
+
+ Bugs fixed:
+
+ - Highlight the correct ZMI tab after submitting anything
+ from the "Mappings" and "Log" tab.
+
+ - Use REQUEST.PATH_INFO instead of REQUEST.URL because
+ PATH_INFO always contains the full path during traversal,
+ whereas URL only contains the path up to the currently
+ traversed element. This fixes a bug that prevented redirecting
+ to a path that had nothing in common with the requested
+ path. Thanks to Josef Meile for pointing this out.
+
+
+ 1.0beta2
+
+ Features added:
+
+ * __call__ is now equivalent to calling the redirect method,
+ making usage even easier. Now you can just activate it like
+ this::
+
+ redirector_object(REQUEST)
+
+
+
+ 1.0beta1
+
+ First working code version
+
+
+ 0.5
+
+ Started putting the files together
+
diff --git a/Products/JRedirector/INSTALL.txt b/Products/JRedirector/INSTALL.txt
new file mode 100644
index 0000000..6270db6
--- /dev/null
+++ b/Products/JRedirector/INSTALL.txt
@@ -0,0 +1,16 @@
+Installing the JRedirector Product
+
+ You will need Zope version 2.4.0 or higher!
+
+ This product does not require any special handling after unzipping
+ and untarring it in the Zope Products directory. You should do
+ something like::
+
+ $ cp JRedirector-xyz.tgz <zope_root>/lib/python/Products
+ $ cd <zope_root>/lib/python/Products
+ $ tar zxvf JRedirector-xyz.tgz
+ <watch files being decompressed>
+
+ That's all. Do not forget to restart Zope afterwards.
+
+ See README.txt for any other dependencies and requirements.
diff --git a/Products/JRedirector/JRedirector.py b/Products/JRedirector/JRedirector.py
new file mode 100644
index 0000000..fbc2cd5
--- /dev/null
+++ b/Products/JRedirector/JRedirector.py
@@ -0,0 +1,308 @@
+##############################################################################
+#
+# JRedirector Perform and log redirections
+#
+# This software is governed by a license. See
+# LICENSE.txt for the terms of this license.
+#
+###############################################################################
+
+__doc__ = """ JRedirector Module """
+__version__='$Revision$'[11:-2]
+
+import os, time
+from DateTime.DateTime import DateTime
+from Acquisition import aq_inner, aq_parent
+from AccessControl.Permissions import view_management_screens, manage_properties
+from AccessControl import ClassSecurityInfo
+from OFS.SimpleItem import SimpleItem
+from BTrees.OOBTree import OOBTree
+from Globals import DTMLFile, InitializeClass, MessageDialog, \
+ PersistentMapping, package_home
+
+_dtmldir = os.path.join(package_home(globals()), 'dtml')
+addJRedirectorForm = DTMLFile('add', _dtmldir)
+
+
+class JRedirector(SimpleItem):
+ """ JRedirector """
+ security = ClassSecurityInfo()
+ meta_type = 'JRedirector'
+
+ manage_log = DTMLFile('dtml/log', globals())
+ manage_map = DTMLFile('dtml/mappings', globals())
+ manage_settings = DTMLFile('dtml/settings', globals())
+ manage = manage_main = manage_settings
+ manage_main._setName('manage_main')
+ manage_settings._setName('manage_settings')
+
+ manage_options = (
+ ( { 'label' : 'Properties', 'action' : 'manage_settings',
+ 'help' : ('JRedirector', 'Properties.stx') }
+ , { 'label' : 'Mappings', 'action' : 'manage_map',
+ 'help' : ('JRedirector', 'Mappings.stx') }
+ , { 'label' : 'Log', 'action' : 'manage_log',
+ 'help' : ('JRedirector', 'Log.stx') }
+ )
+ + SimpleItem.manage_options
+ )
+
+
+ def __init__(self, id, title='', default_redirect='', loglevel=3):
+ """ Initialize a new JRedirector instance """
+ self.id = id
+ self.title = title
+ self._default_redirect = default_redirect
+ self._redirects = PersistentMapping()
+ self._log = OOBTree()
+ self._loglevel = int(loglevel)
+
+
+ security.declarePrivate('_getRedirectData')
+ def _getRedirectData(self, REQUEST):
+ """ Get the redirect URL and log """
+ wildcard_match = 0
+ server_url = REQUEST.get('SERVER_URL')
+ referrer = REQUEST.get('HTTP_REFERER', '').strip() or 'n/a'
+ full_url = REQUEST.get('PATH_INFO')
+
+ if ( full_url.find('VirtualHostBase') != -1 or
+ full_url.find('VirtualHostRoot') != -1 ):
+ full_url = REQUEST.get('URL')
+ trp = list(REQUEST.get('TraversalRequestNameStack'))
+ vpp = REQUEST.get('VirtualRootPhysicalPath')
+
+ if len(trp) > 0:
+ trp.reverse()
+ full_url = '%s/%s' % (full_url, '/'.join(trp))
+
+ nonvirtual_path = full_url.replace(server_url, '')
+ old_path = '%s%s' % ('/'.join(vpp), nonvirtual_path)
+ else:
+ old_path = full_url.replace(server_url, '')
+
+ redir_handled = self._redirects.has_key(old_path)
+
+ if not redir_handled:
+ # Test if this path is to be handled case-insensitively
+ lc_old_path = old_path.lower()
+ if ( self._redirects.has_key(lc_old_path) and
+ self._redirects[lc_old_path].get('case_insensitive', None) ):
+ redir_handled = 1
+ old_path = lc_old_path
+
+ if not redir_handled:
+ # Last resort: does one of the wildcards match?
+ filt = lambda x: x[1].get('is_wildcard', None) and x[0] or None
+ wildcards = filter(None, map(filt, self._redirects.items()))
+ for wildcard in wildcards:
+ wc_dict = self._redirects.get(wildcard)
+ if wc_dict.get('case_insensitive', None):
+ test_against = old_path.lower()
+ else:
+ test_against = old_path
+
+ if test_against.find(wildcard) != -1:
+ old_path = wildcard
+ redir_handled = 1
+ wildcard_match = 1
+ break
+
+ ll = getattr(self, '_loglevel', 3)
+ if ( ll == 3 or
+ (ll == 1 and redir_handled == 0) or
+ (ll == 2 and redir_handled == 1) ):
+ log = self._getLogStore()
+ redir_rec = log.get(old_path, {})
+ redir_count = redir_rec.get('total_count', 0)
+ refs = redir_rec.get('referrers', [])
+ if referrer not in refs:
+ refs.append(referrer)
+
+ get_transaction().abort()
+ log[old_path] = { 'total_count' : redir_count + 1
+ , 'last_access' : time.time()
+ , 'redirected' : redir_handled
+ , 'wildcard_match' : wildcard_match
+ , 'referrers' : refs
+ }
+ get_transaction().note('JRedirector redirected %s' % old_path)
+ get_transaction().commit()
+
+ return self._redirects.get(old_path)
+
+
+ security.declarePublic('redirect')
+ def redirect(self, REQUEST):
+ """ Handle a redirect for a given URL """
+ redir_data = self._getRedirectData(REQUEST)
+
+ if redir_data:
+ redir_path = redir_data.get('redir_path', self._default_redirect)
+ redir_status = redir_data.get('redir_status', '301')
+
+ REQUEST.RESPONSE.redirect( redir_path
+ , status=int(redir_status)
+ , lock=1
+ )
+
+ __call__ = redirect
+
+
+ security.declarePublic('getRedirectURL')
+ def getRedirectURL(self, REQUEST):
+ """ Get URL and status for a redirect """
+ redir_path = ''
+ redir_status = ''
+
+ redir_data = self._getRedirectData(REQUEST)
+
+ if redir_data:
+ redir_path = redir_data.get('redir_path', self._default_redirect)
+ redir_status = redir_data.get('redir_status', '301')
+
+ return redir_path, redir_status
+
+
+ security.declareProtected(manage_properties, 'manage_edit')
+ def manage_edit( self
+ , title=''
+ , default_redirect=''
+ , loglevel=3
+ , REQUEST=None
+ ):
+ """ Edit the JRedirector folder properties """
+ self.title = title
+ self._default_redirect = default_redirect
+ self._loglevel = int(loglevel)
+
+ if REQUEST is not None:
+ msg = 'Properties changed.'
+ return self.manage_settings(manage_tabs_message=msg)
+
+ security.declareProtected(view_management_screens, 'getLoglevel')
+ def getLoglevel(self):
+ """ Return the level of logging """
+ return getattr(self, '_loglevel', 3)
+
+
+ security.declareProtected(view_management_screens, 'getMappings')
+ def getMappings(self):
+ """ Return the existing mappings """
+ return self._redirects.items()
+
+
+ security.declareProtected(view_management_screens, 'getMapping')
+ def getMapping(self, old_url):
+ """ Return a specific mapping """
+ return self._redirects.get(old_url, {})
+
+
+ security.declarePrivate('_getLogStore')
+ def _getLogStore(self):
+ """ Indirection to allow flexibility in backend stores """
+ cur_log = self._log
+
+ if isinstance(cur_log, PersistentMapping):
+ self._log = OOBTree()
+
+ for key, value in cur_log.items():
+ self._log[key] = value
+
+ cur_log = self._log
+
+ return cur_log
+
+
+
+ security.declareProtected(view_management_screens, 'getLog')
+ def getLog(self):
+ """ Return the log """
+ log = self._getLogStore()
+ log_items = list(log.items())
+ sort_f = lambda a, b: cmp(b[1]['total_count'], a[1]['total_count'])
+ log_items.sort(sort_f)
+
+ return log_items
+
+
+ security.declareProtected(view_management_screens, 'clearLog')
+ def clearLog(self, REQUEST=None):
+ """ Clear out the log """
+ self._getLogStore().clear()
+
+ if REQUEST is not None:
+ msg = 'Log cleared'
+ return self.manage_log(manage_tabs_message=msg)
+
+
+
+ security.declareProtected(view_management_screens, 'getDefaultRedirect')
+ def getDefaultRedirect(self):
+ """ Return the default redirect path value """
+ return self._default_redirect
+
+
+ security.declareProtected(manage_properties, 'addRedirectorMapping')
+ def addRedirectorMapping( self
+ , old_path
+ , new_path
+ , redir_status='301'
+ , case_insensitive=0
+ , is_wildcard=0
+ , REQUEST=None
+ ):
+ """ Add a new redirector mapping """
+ if old_path.endswith('/'):
+ old_path = old_path[:-1]
+
+ if case_insensitive:
+ old_path = old_path.lower()
+
+ self._redirects[old_path] = { 'redir_path' : new_path
+ , 'redir_status' : redir_status
+ , 'case_insensitive' : case_insensitive
+ , 'is_wildcard' : is_wildcard
+ }
+
+ if REQUEST is not None:
+ msg = 'Mapping added'
+ return self.manage_map(manage_tabs_message=msg)
+
+
+ security.declareProtected(manage_properties, 'deleteRedirectorMapping')
+ def deleteRedirectorMappings(self, old_paths, REQUEST=None):
+ """ Delete a redirector mapping """
+ for old_path in old_paths:
+ if self._redirects.has_key(old_path):
+ del self._redirects[old_path]
+
+ if REQUEST is not None:
+ msg = 'Mapping deleted'
+ return self.manage_map(manage_tabs_message=msg)
+
+
+def manage_addJRedirector( self
+ , id
+ , title=''
+ , default_redirect=''
+ , loglevel=3
+ , REQUEST=None
+ ):
+ """ Called by Zope if you create a JRedirector from the ZMI """
+
+ if hasattr(self.this().aq_base, id) and REQUEST is not None:
+ msg = 'Object already contains an item with id %s' % id
+ return MessageDialog( title = 'Item Exists'
+ , message = msg
+ , action ='%s/manage_main' % REQUEST['URL1']
+ )
+
+ jr = JRedirector(id, title, default_redirect, loglevel)
+ self._setObject(jr.getId(), jr)
+
+ if REQUEST is not None:
+ return self.manage_main(self, REQUEST)
+
+
+InitializeClass(JRedirector)
diff --git a/Products/JRedirector/LICENSE.txt b/Products/JRedirector/LICENSE.txt
new file mode 100644
index 0000000..706ce9c
--- /dev/null
+++ b/Products/JRedirector/LICENSE.txt
@@ -0,0 +1,77 @@
+##############################################################################
+#
+# License
+# -------
+#
+# Copyright (c) Jens Vagelpohl. All right reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions in source code must retain the above copyright
+# notice, this list of conditions, and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions, and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# 3. Jens Vagelpohl requests that attribution be given to Zope
+# in any manner possible. A significant investment has been put
+# into Zope, and this effort will continue if the Zope community
+# continues to grow. This is one way to assure that growth.
+#
+# 4. All advertising materials and documentation mentioning
+# features derived from or use of this software must display
+# the following acknowledgement:
+#
+# "This product includes software developed by Jens Vagelpohl
+# for use in the Z Object Publishing Environment
+# (http://www.zope.org/)."
+#
+# In the event that the product being advertised includes an
+# intact JRedirector distribution (with copyright and license
+# included) then this clause is waived
+#
+# 5. Names associated with Jens Vagelpohl must not be used to
+# endorse or promote products derived from this software without
+# prior written permission from Jens Vagelpohl.
+#
+# 6. Modified redistributions of any form whatsoever must retain
+# the following acknowledgment:
+#
+# "This product includes software developed by Jens Vagelpohl
+# for use in the Z Object Publishing Environment
+# (http://www.zope.org/)."
+#
+# Intact (re-)distributions of any official JRedirector
+# release do not require an external acknowledgement.
+#
+# 7. Modifications are encouraged but must be packaged separately as
+# patches to official software releases. Distributions that do not
+# clearly separate the patches from the original work must be clearly
+# labeled as unofficial distributions.
+#
+# 8. In the interest of improving this product the author requests that
+# changes be made available to him. This is not mandatory but part
+# of good open source development etiquette.
+#
+#
+# Disclaimer
+#
+# THIS SOFTWARE IS PROVIDED BY JENS VAGELPOHL ``AS IS'' AND ANY
+# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JENS VAGELPOHL OR HIS
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+##############################################################################
+
diff --git a/Products/JRedirector/README.txt b/Products/JRedirector/README.txt
new file mode 100644
index 0000000..7576e28
--- /dev/null
+++ b/Products/JRedirector/README.txt
@@ -0,0 +1,161 @@
+README for the Zope JRedirector Product
+
+ The JRedirector package provides an object that is capable of
+ redirecting web requests in a controlled fashion and keeping logs
+ about it.
+
+ I wrote it so that when I move pieces of my sites around I have
+ a way of specifying where users will go if they navigate to the old
+ obsolete location. This is helpful if your site is linked from
+ other sites and you have no control over the accuracy of these
+ outside links.
+
+ The administrator can add mappings from old path to new path where
+ the user will be redirected to when he tries to visit the old path.
+ The HTTP header sent along with this redirect can be specified,
+ available choices are "301" (moved permanently) or "302" (moved
+ temporarily).
+
+ The object will keep an internal log of all web requests that are
+ referred to it and presents it on a logging output page.
+
+
+ **Usage**
+
+ The administrator creates a JRedirector object in a given
+ location in site. Invoking the redirection capabilities must happen
+ explicitly, for example from standard_error_message, by calling
+ the JRedirector object and passing REQUEST.
+
+ As an example, here is the snippet of my standard_html_error that
+ invokes the JRedirector object:
+
+ < dtml-if expr="error_type == 'NotFound'">
+ < dtml-call expr="redirector_object(REQUEST)">
+ < /dtml-if>
+
+ This will fire whenever a "NotFound" error occurs. If the path
+ the user attempted to go to is not in the explicitly mapped
+ list of paths defined by the administrator in the JRedirector
+ object "Mappings" tab then nothing will happen and the
+ standard_error_message will continue to render normally. If the
+ looked-for path is explicitly mapped then the user will be
+ redirected and will never see standard_error_message.
+
+
+ **Requirements**
+
+ In order for this product to run you will need to provide the
+ following items:
+
+ * A running Zope site version 2.4 or higher
+
+
+ **Tested Platforms and versions**
+
+ This product has been written on and for Zope 2.4.0 and up. I am
+ not going to support earlier versions of Zope with my product.
+
+
+ **HTTP Status Codes related to redirecting requests**
+
+ The following is taken from RFC 2616 which describes the HTTP/1.1
+ specification. The RFCs can be found in various locations on the
+ Internet, I found it here::
+
+ ftp://ftp.isi.edu/in-notes/rfc2616.txt
+
+ Not all status codes are understood by all browsers. If you are worried
+ about older browsers you should restrict your usage to status codes
+ 301 for permanently moved pages and 302 for temporary moves.
+
+
+ **301 Moved Permanently**
+
+ The requested resource has been assigned a new permanent URI and any
+ future references to this resource SHOULD use one of the returned
+ URIs. Clients with link editing capabilities ought to automatically
+ re-link references to the Request-URI to one or more of the new
+ references returned by the server, where possible. This response is
+ cacheable unless indicated otherwise.
+
+ The new permanent URI SHOULD be given by the Location field in the
+ response. Unless the request method was HEAD, the entity of the
+ response SHOULD contain a short hypertext note with a hyperlink to
+ the new URI(s).
+
+ If the 301 status code is received in response to a request other
+ than GET or HEAD, the user agent MUST NOT automatically redirect the
+ request unless it can be confirmed by the user, since this might
+ change the conditions under which the request was issued.
+
+ Note: When automatically redirecting a POST request after
+ receiving a 301 status code, some existing HTTP/1.0 user agents
+ will erroneously change it into a GET request.
+
+ **302 Found**
+
+ The requested resource resides temporarily under a different URI.
+ Since the redirection might be altered on occasion, the client SHOULD
+ continue to use the Request-URI for future requests. This response
+ is only cacheable if indicated by a Cache-Control or Expires header
+ field.
+
+ The temporary URI SHOULD be given by the Location field in the
+ response. Unless the request method was HEAD, the entity of the
+ response SHOULD contain a short hypertext note with a hyperlink to
+ the new URI(s).
+
+ If the 302 status code is received in response to a request other
+ than GET or HEAD, the user agent MUST NOT automatically redirect the
+ request unless it can be confirmed by the user, since this might
+ change the conditions under which the request was issued.
+
+ Note: RFC 1945 and RFC 2068 specify that the client is not allowed
+ to change the method on the redirected request. However, most
+ existing user agent implementations treat 302 as if it were a 303
+ response, performing a GET on the Location field-value regardless
+ of the original request method. The status codes 303 and 307 have
+ been added for servers that wish to make unambiguously clear which
+ kind of reaction is expected of the client.
+
+ **303 See Other**
+
+ The response to the request can be found under a different URI and
+ SHOULD be retrieved using a GET method on that resource. This method
+ exists primarily to allow the output of a POST-activated script to
+ redirect the user agent to a selected resource. The new URI is not a
+ substitute reference for the originally requested resource. The 303
+ response MUST NOT be cached, but the response to the second
+ (redirected) request might be cacheable.
+
+ The different URI SHOULD be given by the Location field in the
+ response. Unless the request method was HEAD, the entity of the
+ response SHOULD contain a short hypertext note with a hyperlink to
+ the new URI(s).
+
+ Note: Many pre-HTTP/1.1 user agents do not understand the 303
+ status. When interoperability with such clients is a concern, the
+ 302 status code may be used instead, since most user agents react
+ to a 302 response as described here for 303.
+
+ **307 Temporary Redirect**
+
+ The requested resource resides temporarily under a different URI.
+ Since the redirection MAY be altered on occasion, the client SHOULD
+ continue to use the Request-URI for future requests. This response
+ is only cacheable if indicated by a Cache-Control or Expires header
+ field.
+
+ The temporary URI SHOULD be given by the Location field in the
+ response. Unless the request method was HEAD, the entity of the
+ response SHOULD contain a short hypertext note with a hyperlink to
+ the new URI(s) , since many pre-HTTP/1.1 user agents do not
+ understand the 307 status. Therefore, the note SHOULD contain the
+ information necessary for a user to repeat the original request on
+ the new URI.
+
+ If the 307 status code is received in response to a request other
+ than GET or HEAD, the user agent MUST NOT automatically redirect the
+ request unless it can be confirmed by the user, since this might
+ change the conditions under which the request was issued.
diff --git a/Products/JRedirector/VERSION.txt b/Products/JRedirector/VERSION.txt
new file mode 100644
index 0000000..7e32cd5
--- /dev/null
+++ b/Products/JRedirector/VERSION.txt
@@ -0,0 +1 @@
+1.3
diff --git a/Products/JRedirector/__init__.py b/Products/JRedirector/__init__.py
new file mode 100644
index 0000000..1d8fbcd
--- /dev/null
+++ b/Products/JRedirector/__init__.py
@@ -0,0 +1,24 @@
+##############################################################################
+#
+# __init__.py Initialization code for the JRedirector package
+#
+# This software is governed by a license. See
+# LICENSE.txt for the terms of this license.
+#
+##############################################################################
+
+__doc__ = """ JRedirector initialization module """
+__version__ = '$Revision$'[11:-2]
+
+from JRedirector import JRedirector, manage_addJRedirector, addJRedirectorForm
+
+def initialize(context):
+ context.registerClass( JRedirector
+ , permission='Add JRedirector'
+ , constructors=( addJRedirectorForm
+ , manage_addJRedirector
+ )
+ , icon='www/jredirector.gif'
+ )
+
+ context.registerHelp()
diff --git a/Products/JRedirector/dtml/add.dtml b/Products/JRedirector/dtml/add.dtml
new file mode 100644
index 0000000..17e1b97
--- /dev/null
+++ b/Products/JRedirector/dtml/add.dtml
@@ -0,0 +1,71 @@
+<dtml-var manage_page_header>
+
+<dtml-var "manage_form_title( this()
+ , _
+ , form_title='Add JRedirector'
+ , help_product='JRedirector'
+ , help_topic='Add.stx'
+ )">
+
+<p class="form-help">
+ Use this form to add a JRedirector object.
+</p>
+
+<p>&nbsp;</p>
+
+<form action="manage_addJRedirector" method="post">
+<table cellspacing="0" cellpadding="2" border="0">
+
+ <tr>
+ <td align="left" valign="top"><div class="form-label">
+ ID
+ </div></td>
+ <td align="left" valign="top"><div class="form-element">
+ <input type="text" name="id" size="40" />
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top"><div class="form-optional">
+ Title
+ </div></td>
+ <td align="left" valign="top"><div class="form-element">
+ <input type="text" name="title" size="40" />
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top"><div class="form-optional">
+ Default Redirect Path
+ </div></td>
+ <td align="left" valign="top"><div class="form-element">
+ <input type="text" name="default_redirect" size="40" />
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top"><div class="form-label">
+ Log Level
+ </div></td>
+ <td align="left" valign="top"><div class="form-element">
+ <select name="loglevel:int">
+ <option value="0"> No logging at all </option>
+ <option value="1"> Log unsuccessful redirections </option>
+ <option value="2"> Log successful redirections </option>
+ <option value="3"> Log everything </option>
+ </select>
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">&nbsp;</td>
+ <td align="left" valign="top"><div class="form-element">
+ <input class="form-element" type="submit" name="submit"
+ value=" Add " />
+ </div></td>
+ </tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>
+
diff --git a/Products/JRedirector/dtml/log.dtml b/Products/JRedirector/dtml/log.dtml
new file mode 100644
index 0000000..87b2652
--- /dev/null
+++ b/Products/JRedirector/dtml/log.dtml
@@ -0,0 +1,77 @@
+<dtml-var manage_page_header>
+
+<dtml-with "_(management_view='Log')">
+ <dtml-var manage_tabs>
+</dtml-with>
+
+<p class="form-help">
+ This view shows the URLs handled through the JRedirector,
+ the last time and the total number of times they were encountered.
+ Red font indicates that the URL is not explicitly redirected by
+ the JRedirector. An Asterisk (*) after the URL specifies wildcard
+ matches.
+</p>
+
+<form method="post" action="clearLog">
+ <input type="submit" value=" Clear Log ">
+</form>
+
+<p><hr /></p>
+
+<dtml-in getLog>
+
+ <dtml-if name="sequence-start">
+ <table width="98%" cellpadding="2" border="1">
+ <tr class="list-header">
+ <th width="40">Hits</th>
+ <th width="90">Last Accessed</th>
+ <th>URL and Referrers</th>
+ </tr>
+ </dtml-if>
+
+ <dtml-let si=sequence-item
+ lt="ZopeTime(si.get('last_access', 0))"
+ la="lt.aCommon()"
+ tt="si.get('total_count', 0)"
+ rh="si.get('redirected', 0)"
+ wc="si.get('wildcard_match', 0)"
+ >
+ <tr>
+ <td align="right" valign="top" width="40"><div class="form-text">
+ &dtml-tt;
+ </div></td>
+ <td align="center" valign="top" width="90"><div class="form-text">
+ <dtml-var expr="lt.Date()"> <br />
+ <dtml-var expr="lt.Time()">
+ </div></td>
+ <td align="left" valign="top"><div class="form-text">
+ <dtml-if "not rh"><font color="red"></dtml-if>
+ &dtml-sequence-key;<dtml-if wc>*</dtml-if>
+ <dtml-if "not rh"></font></dtml-if>
+ <dtml-in expr="si.get('referrers', [])">
+ <dtml-if sequence-start>
+ <br /><br />
+ <b>Referrers:</b><br />
+ </dtml-if>
+ <dtml-if "_['sequence-item'] == 'n/a'">
+ &nbsp;&nbsp;&nbsp;&dtml-sequence-item;
+ <dtml-else>
+ <a href="&dtml-sequence-item;" target="_blank">
+ &nbsp;&nbsp;&nbsp;&dtml-sequence-item;
+ </a><br />
+ </dtml-if>
+ </dtml-in>
+ </div></td>
+ </tr>
+ </dtml-let>
+
+ <dtml-if name="sequence-end">
+ </table>
+ </dtml-if>
+
+<dtml-else>
+ <p><b>The log is curently empty.</b></p>
+
+</dtml-in>
+
+<dtml-var manage_page_footer>
diff --git a/Products/JRedirector/dtml/mappings.dtml b/Products/JRedirector/dtml/mappings.dtml
new file mode 100644
index 0000000..d0d7abd
--- /dev/null
+++ b/Products/JRedirector/dtml/mappings.dtml
@@ -0,0 +1,182 @@
+<dtml-var manage_page_header>
+
+<dtml-with "_(management_view='Mappings')">
+ <dtml-var manage_tabs>
+</dtml-with>
+
+<style type="text/css">
+<!--
+
+.oldlink, a:link.oldlink, a:visited.oldlink {
+ color: red;
+ text-decoration: None;
+ }
+
+.newlink, a:link.newlink, a:visited.newlink {
+ color: green;
+ text-decoration: None;
+ }
+
+-->
+</style>
+
+<p class="form-help">
+ Use this view to manage the mappings that describe redirects
+ from paths that are handled by this redirector.
+</p>
+
+<p class="form-help">
+ For information about the HTTP response codes listed please
+ refer to README.txt in this product or to RFC 2616, which
+ fully describes the HTTP/1.1 specification.
+ If you are unsure about which response code to choose you should
+ restrict yourself to 301 for permanently moved pages and 302
+ if the new path is only used temporarily.
+</p>
+
+<p class="form-help">
+ You can test your links by clicking on the Old Path and New Path.
+ If everything works correctly (and the path set as Old Path does
+ not exist anymore) both links should go to New Path.
+</p>
+
+<dtml-in getMappings>
+
+ <dtml-if name="sequence-start">
+ <form method="post" action="deleteRedirectorMappings">
+ <table width="98%" cellspacing="0" cellpadding="2">
+ <tr class="list-header">
+ <td align="left" valign="top" width="16">&nbsp;</td>
+ <td><div class="form-label">
+ <span class="oldlink"> Old Path </span> -->
+ <span class="newlink"> New Path </span>
+ </div></td>
+ <td><div class="form-label"> Response code </div></td>
+ <td><div class="form-label"> Case-sensitive </div></td>
+ <td><div class="form-label"> Wildcard </div></td>
+ </tr>
+ </dtml-if>
+
+ <dtml-let si=sequence-item>
+ <dtml-if sequence-odd>
+ <tr class="row-normal">
+ <dtml-else>
+ <tr class="row-hilite">
+ </dtml-if>
+ <td align="left" valign="top" width="16">
+ <input type="checkbox" name="old_paths:list"
+ value="&dtml-sequence-key;" />
+ </td>
+ <td><div class="list-item">
+ <dtml-let old=sequence-key
+ new="si.get( 'redir_path', '' )">
+ <a class="oldlink" href="&dtml-old;" target="_blank">
+ &dtml-old;
+ </a>
+ --> <br />
+ <a class="newlink" href="&dtml-new;" target="_blank">
+ &dtml-new;
+ </a>
+ </dtml-let>
+ </td>
+ <td><div class="list-item">
+ <dtml-var expr="si.get( 'redir_status', '' )">
+ </div></td>
+ <td><div class="list-item">
+ <dtml-let cis="si.get('case_insensitive', '')">
+ <dtml-var expr="not cis and 'No' or 'Yes'">
+ </dtml-let>
+ </div></td>
+ <td><div class="list-item">
+ <dtml-var expr="si.get('is_wildcard', '') and 'Yes' or 'No'">
+ </div></td>
+ </tr>
+ </dtml-let>
+
+ <dtml-if name="sequence-end">
+ <tr>
+ <td align="left" valign="top" colspan="5"><div class="form-element">
+ <br />
+ <input class="form-element" type="submit" value=" Delete " />
+ </div><br /></td>
+ </tr>
+ </table>
+ </form>
+ </dtml-if>
+
+<dtml-else>
+ <p><b>No mappings have been defined yet.</b></p>
+
+</dtml-in>
+
+<p>&nbsp;</p>
+
+<form action="addRedirectorMapping" method="post">
+<table cellspacing="0" cellpadding="2" width="98%">
+ <tr class="list-header">
+ <td colspan="2"><div class="form-label">
+ Add Mapping
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="LEFT" valign="TOP"><div class="form-label">
+ Old Path
+ </div></td>
+ <td align="left" valign="top">
+ <input type="text" name="old_path" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="LEFT" valign="TOP"><div class="form-label">
+ New Path
+ </div></td>
+ <td align="left" valign="top">
+ <input type="text" name="new_path" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="LEFT" valign="TOP"><div class="form-label">
+ Response Code
+ </div></td>
+ <td align="left" valign="top">
+ <select name="redir_status">
+ <option value="301"> 301 - Moved Permanently </option>
+ <option value="302"> 302 - Moved Temporarily (Found) </option>
+ <option value="303"> 303 - See Other </option>
+ <option value="307"> 307 - Temporary Redirect </option>
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="LEFT" valign="TOP"><div class="form-label">
+ Case-insensitive old path handling
+ </div></td>
+ <td align="left" valign="top">
+ <input type="checkbox" name="case_insensitive">
+ </td>
+ </tr>
+
+ <tr>
+ <td align="LEFT" valign="TOP"><div class="form-label">
+ Wildcard Mapping
+ </div></td>
+ <td align="left" valign="top">
+ <input type="checkbox" name="is_wildcard">
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top" colspan="2"><div class="form-element">
+ <br />
+ <input class="form-element" type="SUBMIT" value=" Add " />
+ </div></td>
+ </tr>
+
+</table>
+</form>
+
+<dtml-var manage_page_footer>
diff --git a/Products/JRedirector/dtml/settings.dtml b/Products/JRedirector/dtml/settings.dtml
new file mode 100644
index 0000000..1b3b795
--- /dev/null
+++ b/Products/JRedirector/dtml/settings.dtml
@@ -0,0 +1,66 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+ Change the basic properties for the JRedirector on this form.
+</p>
+
+<form action="manage_edit" method="post">
+<table cellspacing="0" cellpadding="2" border="0">
+
+ <tr>
+ <td align="left" valign="top"><div class="form-optional">
+ Title
+ </div></td>
+ <td align="left" valign="top"><div class="form-element">
+ <input type="text" name="title" size="40"
+ value="&dtml.missing-title;"/>
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top"><div class="form-optional">
+ Default Redirect Path
+ </div></td>
+ <td align="left" valign="top"><div class="form-element">
+ <input type="text" name="default_redirect" size="40"
+ value="&dtml-getDefaultRedirect;" />
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top"><div class="form-label">
+ Log Level
+ </div></td>
+ <td align="left" valign="top"><div class="form-element">
+ <select name="loglevel:int">
+ <dtml-let ll=getLoglevel>
+ <option value="0" <dtml-if "ll == 0">selected</dtml-if>>
+ No logging at all
+ </option>
+ <option value="1" <dtml-if "ll == 1">selected</dtml-if>>
+ Log unsuccessful redirections
+ </option>
+ <option value="2" <dtml-if "ll == 2">selected</dtml-if>>
+ Log successful redirections
+ </option>
+ <option value="3" <dtml-if "ll == 3">selected</dtml-if>>
+ Log everything
+ </option>
+ </dtml-let>
+ </select>
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">&nbsp;</td>
+ <td align="left" valign="top"><div class="form-element">
+ <input class="form-element" type="submit" name="submit"
+ value=" Apply Changes " />
+ </div></td>
+ </tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>
+
diff --git a/Products/JRedirector/help/Add.stx b/Products/JRedirector/help/Add.stx
new file mode 100644
index 0000000..a53c3af
--- /dev/null
+++ b/Products/JRedirector/help/Add.stx
@@ -0,0 +1,52 @@
+JRedirector - Add: Create a new JRedirector object
+
+ Description
+
+ The JRedirector package provides an object that is capable of
+ redirecting web requests in a controlled fashion and keeping logs
+ about it.
+
+ I wrote it so that when I move pieces of my sites around I have
+ a way of specifying where users will go if they navigate to the old
+ obsolete location. This is helpful if your site is linked from
+ other sites and you have no control over the accuracy of these
+ outside links.
+
+ The administrator can add mappings from old path to new path where
+ the user will be redirected to when he tries to visit the old path.
+ The HTTP header sent along with this redirect can be specified,
+ available choices are "301" (moved permanently) or "302" (moved
+ temporarily).
+
+ The object will keep an internal log of all web requests that are
+ referred to it and presents it on a logging output page.
+
+
+ Controls
+
+ 'ID' -- The ID (or name) for the JRedirector instance
+
+ 'Title' -- The (optional) title for this object
+
+ 'Default Redirect Path' -- The default redirection that happens if a
+ path found in the database of explicit mappings does not have a
+ new path it wants to redirect to.
+
+ 'Log Level' -- Specify what events to log. On a very busy site it is
+ possible that the log grows very fast and, with it, your ZODB. You
+ can select from the following options:
+
+ - No logging at all will not write logs.
+
+ - Log unsuccessful redirections will log all those events where
+ your JRedirector instance got called but did not have the URL
+ in question in its mapping of redirected paths.
+
+ - Log successful redirections will only log those events where
+ a URL was successfully redirected.
+
+ - Log everything simply combines logging of successful and
+ unsuccessful redirection events.
+
+ 'Add' -- Instantiate the JRedirector object.
+
diff --git a/Products/JRedirector/help/Log.stx b/Products/JRedirector/help/Log.stx
new file mode 100644
index 0000000..4d0adc0
--- /dev/null
+++ b/Products/JRedirector/help/Log.stx
@@ -0,0 +1,23 @@
+JRedirector - Log: View log information
+
+ Description
+
+ This screen shows the log kept by the JRedirector. The log
+ display is sorted by the number of times the logged paths
+ were accessed, in descending order.
+
+ A basic log entry has the following elements:
+
+ o The path the JRedirector was asked to handle. If the URL is
+ in red letters then there is no explicit mapping defined for
+ it on the "Mappings" tab. If the URL is followed by a "*"
+ (Asterisk) then the match was a wildcard match.
+
+ o The last time this path was encountered by the JRedirector.
+
+ o The total number of times the path was encountered.
+
+ Controls
+
+ 'Clear Log' -- Clear out the log
+
diff --git a/Products/JRedirector/help/Mappings.stx b/Products/JRedirector/help/Mappings.stx
new file mode 100644
index 0000000..f886cde
--- /dev/null
+++ b/Products/JRedirector/help/Mappings.stx
@@ -0,0 +1,43 @@
+JRedirector - Mappings: Set explicit path redirection
+
+ Description
+
+ This view is used to view and delete existing mappings as well as
+ add new mappings to the list.
+
+ A mapping consists of three elements: Old Path, New Path, and
+ Response Code. The old path is a path that you want to redirect
+ away from, maybe because the object that is at the end of the path
+ has been deleted. The new path is the place where users are going
+ when they are trying to get the old path. The response code determines
+ the HTTP response code that will get set for the redirect.
+
+ Controls
+
+ 'Delete' -- In order to remove items from the list of mappings you can
+ select one or more checkboxes and hit "Delete" to remove them from the
+ list.
+
+ 'Old Path' -- Enter the obsolete path for the redirection here. Do not
+ include the server URL. The format is '/path/to/object'.
+
+ 'New Path' -- Where the users will end up when they hit the old path.
+ Do not include the server URL. The format is '/path/to/object'.
+
+ 'Response Code' -- The HTTP Response code set during the redirection.
+ Please see README.txt for an explanation of these codes. If you are
+ unsure what to use simply select 301 for all permanently moved pages
+ and 302 if you intend to move the page back to the old path at some
+ point.
+
+ 'Case-insensitive old path handling' -- If this box is checked then
+ the old path coming from the client is matched up without regard to
+ uppercase/lowercase.
+
+ 'Wildcard Mapping' -- Checking this box will attempt to match
+ all those paths *at or underneath the path specified as Old Path*
+ that are not matched by any non-wildcard rule. This enables
+ setting a redirect that matches a multitude of requested paths and
+ redirects them to a single new path.
+
+ 'Add' -- Add the new mapping to the list of mappings.
diff --git a/Products/JRedirector/help/Properties.stx b/Products/JRedirector/help/Properties.stx
new file mode 100644
index 0000000..44fb8c2
--- /dev/null
+++ b/Products/JRedirector/help/Properties.stx
@@ -0,0 +1,31 @@
+JRedirector - Properties: Set the basic configuration for the JRedirector
+
+ Description
+
+ This view is used to change the basic settings of a JRedirector.
+
+ Controls
+
+ 'Title' -- The (optional) title.
+
+ 'Default Redirect Path' -- The default redirection that happens if a
+ path found in the database of explicit mappings does not have a
+ new path it wants to redirect to.
+
+ 'Log Level' -- Specify what events to log. On a very busy site it is
+ possible that the log grows very fast and, with it, your ZODB. You
+ can select from the following options:
+
+ - No logging at all will not write logs.
+
+ - Log unsuccessful redirections will log all those events where
+ your JRedirector instance got called but did not have the URL
+ in question in its mapping of redirected paths.
+
+ - Log successful redirections will only log those events where
+ a URL was successfully redirected.
+
+ - Log everything simply combines logging of successful and
+ unsuccessful redirection events.
+
+ 'Apply Changes' -- Save your configuration changes.
diff --git a/Products/JRedirector/www/jredirector.gif b/Products/JRedirector/www/jredirector.gif
new file mode 100644
index 0000000..a517943
--- /dev/null
+++ b/Products/JRedirector/www/jredirector.gif
Binary files differ