mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Content server: Workaround for android stock browser not support HTTP AUTH.
This commit is contained in:
parent
103854e242
commit
1ed30fdcb5
@ -15,7 +15,7 @@ from cherrypy.process.plugins import SimplePlugin
|
||||
from calibre.constants import __appname__, __version__
|
||||
from calibre.utils.date import fromtimestamp
|
||||
from calibre.library.server import listen_on, log_access_file, log_error_file
|
||||
from calibre.library.server.utils import expose
|
||||
from calibre.library.server.utils import expose, AuthController
|
||||
from calibre.utils.mdns import publish as publish_zeroconf, \
|
||||
stop_server as stop_zeroconf, get_external_ip
|
||||
from calibre.library.server.content import ContentServer
|
||||
@ -31,10 +31,11 @@ from calibre import prints, as_unicode
|
||||
|
||||
class DispatchController(object): # {{{
|
||||
|
||||
def __init__(self, prefix, wsgi=False):
|
||||
def __init__(self, prefix, wsgi=False, auth_controller=None):
|
||||
self.dispatcher = cherrypy.dispatch.RoutesDispatcher()
|
||||
self.funcs = []
|
||||
self.seen = set()
|
||||
self.auth_controller = auth_controller
|
||||
self.prefix = prefix if prefix else ''
|
||||
if wsgi:
|
||||
self.prefix = ''
|
||||
@ -44,6 +45,7 @@ class DispatchController(object): # {{{
|
||||
raise NameError('Route name: '+ repr(name) + ' already used')
|
||||
self.seen.add(name)
|
||||
kwargs['action'] = 'f_%d'%len(self.funcs)
|
||||
aw = kwargs.pop('android_workaround', False)
|
||||
if route != '/':
|
||||
route = self.prefix + route
|
||||
elif self.prefix:
|
||||
@ -52,6 +54,8 @@ class DispatchController(object): # {{{
|
||||
self.dispatcher.connect(name+'prefix_extra_trailing',
|
||||
self.prefix+'/', self, **kwargs)
|
||||
self.dispatcher.connect(name, route, self, **kwargs)
|
||||
if self.auth_controller is not None:
|
||||
func = self.auth_controller(func, aw)
|
||||
self.funcs.append(expose(func))
|
||||
|
||||
def __getattr__(self, attr):
|
||||
@ -156,6 +160,8 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
||||
self.config = {}
|
||||
self.is_running = False
|
||||
self.exception = None
|
||||
auth_controller = None
|
||||
self.users_dict = {}
|
||||
#self.config['/'] = {
|
||||
# 'tools.sessions.on' : True,
|
||||
# 'tools.sessions.timeout': 60, # Session times out after 60 minutes
|
||||
@ -171,15 +177,12 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
||||
}
|
||||
|
||||
if opts.password:
|
||||
self.config['/'] = {
|
||||
'tools.digest_auth.on' : True,
|
||||
'tools.digest_auth.realm' : (
|
||||
'Your calibre library. Username: '
|
||||
+ opts.username.strip()),
|
||||
'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()},
|
||||
}
|
||||
self.users_dict[opts.username.strip()] = opts.password.strip()
|
||||
auth_controller = AuthController('Your calibre library',
|
||||
self.users_dict)
|
||||
|
||||
self.__dispatcher__ = DispatchController(self.opts.url_prefix, wsgi)
|
||||
self.__dispatcher__ = DispatchController(self.opts.url_prefix,
|
||||
wsgi=wsgi, auth_controller=auth_controller)
|
||||
for x in self.__class__.__bases__:
|
||||
if hasattr(x, 'add_routes'):
|
||||
x.__init__(self)
|
||||
|
@ -41,7 +41,8 @@ class ContentServer(object):
|
||||
connect('root', '/', self.index)
|
||||
connect('old', '/old', self.old)
|
||||
connect('get', '/get/{what}/{id}', self.get,
|
||||
conditions=dict(method=["GET", "HEAD"]))
|
||||
conditions=dict(method=["GET", "HEAD"]),
|
||||
android_workaround=True)
|
||||
connect('static', '/static/{name:.*?}', self.static,
|
||||
conditions=dict(method=["GET", "HEAD"]))
|
||||
connect('favicon', '/favicon.png', self.favicon,
|
||||
|
@ -5,11 +5,12 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import time, sys
|
||||
import time, sys, uuid, hashlib
|
||||
from urllib import quote as quote_, unquote as unquote_
|
||||
from functools import wraps
|
||||
|
||||
import cherrypy
|
||||
from cherrypy.lib.auth_digest import digest_auth, get_ha1_dict_plain
|
||||
|
||||
from calibre import strftime as _strftime, prints, isbytestring
|
||||
from calibre.utils.date import now as nowf
|
||||
@ -58,6 +59,53 @@ def expose(func):
|
||||
|
||||
return do
|
||||
|
||||
class AuthController(object):
|
||||
|
||||
MAX_AGE = 3600 # Number of seconds after a successful digest auth for which
|
||||
# the cookie auth will be allowed
|
||||
|
||||
def __init__(self, realm, users_dict):
|
||||
self.realm = realm
|
||||
self.users_dict = users_dict
|
||||
self.secret = bytes(uuid.uuid4().hex)
|
||||
self.cookie_name = 'android_workaround'
|
||||
|
||||
def hashit(self, raw):
|
||||
return hashlib.sha1(raw).hexdigest()
|
||||
|
||||
def __call__(self, func, allow_cookie_auth):
|
||||
|
||||
@wraps(func)
|
||||
def authenticate(*args, **kwargs):
|
||||
cookie = cherrypy.request.cookie.get(self.cookie_name, None)
|
||||
if not (allow_cookie_auth and self.is_valid(cookie)):
|
||||
digest_auth(self.realm, get_ha1_dict_plain(self.users_dict),
|
||||
self.secret)
|
||||
|
||||
cookie = cherrypy.response.cookie
|
||||
cookie[self.cookie_name] = self.generate_cookie()
|
||||
cookie[self.cookie_name]['path'] = '/'
|
||||
cookie[self.cookie_name]['version'] = '1'
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
authenticate.im_self = func.im_self
|
||||
return authenticate
|
||||
|
||||
def generate_cookie(self, timestamp=None):
|
||||
timestamp = int(time.time()) if timestamp is None else timestamp
|
||||
key = self.hashit('%d:%s'%(timestamp, self.secret))
|
||||
return '%d:%s'%(timestamp, key)
|
||||
|
||||
def is_valid(self, cookie):
|
||||
try:
|
||||
timestamp, hashpart = cookie.value.split(':', 1)
|
||||
timestamp = int(timestamp)
|
||||
except:
|
||||
return False
|
||||
s_timestamp, s_hashpart = self.generate_cookie(timestamp).split(':', 1)
|
||||
is_valid = s_hashpart == hashpart
|
||||
return (is_valid and (time.time() - timestamp) < self.MAX_AGE)
|
||||
|
||||
def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
|
||||
if not hasattr(dt, 'timetuple'):
|
||||
|
Loading…
x
Reference in New Issue
Block a user