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.constants import __appname__, __version__
|
||||||
from calibre.utils.date import fromtimestamp
|
from calibre.utils.date import fromtimestamp
|
||||||
from calibre.library.server import listen_on, log_access_file, log_error_file
|
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, \
|
from calibre.utils.mdns import publish as publish_zeroconf, \
|
||||||
stop_server as stop_zeroconf, get_external_ip
|
stop_server as stop_zeroconf, get_external_ip
|
||||||
from calibre.library.server.content import ContentServer
|
from calibre.library.server.content import ContentServer
|
||||||
@ -31,10 +31,11 @@ from calibre import prints, as_unicode
|
|||||||
|
|
||||||
class DispatchController(object): # {{{
|
class DispatchController(object): # {{{
|
||||||
|
|
||||||
def __init__(self, prefix, wsgi=False):
|
def __init__(self, prefix, wsgi=False, auth_controller=None):
|
||||||
self.dispatcher = cherrypy.dispatch.RoutesDispatcher()
|
self.dispatcher = cherrypy.dispatch.RoutesDispatcher()
|
||||||
self.funcs = []
|
self.funcs = []
|
||||||
self.seen = set()
|
self.seen = set()
|
||||||
|
self.auth_controller = auth_controller
|
||||||
self.prefix = prefix if prefix else ''
|
self.prefix = prefix if prefix else ''
|
||||||
if wsgi:
|
if wsgi:
|
||||||
self.prefix = ''
|
self.prefix = ''
|
||||||
@ -44,6 +45,7 @@ class DispatchController(object): # {{{
|
|||||||
raise NameError('Route name: '+ repr(name) + ' already used')
|
raise NameError('Route name: '+ repr(name) + ' already used')
|
||||||
self.seen.add(name)
|
self.seen.add(name)
|
||||||
kwargs['action'] = 'f_%d'%len(self.funcs)
|
kwargs['action'] = 'f_%d'%len(self.funcs)
|
||||||
|
aw = kwargs.pop('android_workaround', False)
|
||||||
if route != '/':
|
if route != '/':
|
||||||
route = self.prefix + route
|
route = self.prefix + route
|
||||||
elif self.prefix:
|
elif self.prefix:
|
||||||
@ -52,6 +54,8 @@ class DispatchController(object): # {{{
|
|||||||
self.dispatcher.connect(name+'prefix_extra_trailing',
|
self.dispatcher.connect(name+'prefix_extra_trailing',
|
||||||
self.prefix+'/', self, **kwargs)
|
self.prefix+'/', self, **kwargs)
|
||||||
self.dispatcher.connect(name, route, 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))
|
self.funcs.append(expose(func))
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
@ -156,6 +160,8 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
self.config = {}
|
self.config = {}
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.exception = None
|
self.exception = None
|
||||||
|
auth_controller = None
|
||||||
|
self.users_dict = {}
|
||||||
#self.config['/'] = {
|
#self.config['/'] = {
|
||||||
# 'tools.sessions.on' : True,
|
# 'tools.sessions.on' : True,
|
||||||
# 'tools.sessions.timeout': 60, # Session times out after 60 minutes
|
# 'tools.sessions.timeout': 60, # Session times out after 60 minutes
|
||||||
@ -171,15 +177,12 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.password:
|
if opts.password:
|
||||||
self.config['/'] = {
|
self.users_dict[opts.username.strip()] = opts.password.strip()
|
||||||
'tools.digest_auth.on' : True,
|
auth_controller = AuthController('Your calibre library',
|
||||||
'tools.digest_auth.realm' : (
|
self.users_dict)
|
||||||
'Your calibre library. Username: '
|
|
||||||
+ opts.username.strip()),
|
|
||||||
'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()},
|
|
||||||
}
|
|
||||||
|
|
||||||
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__:
|
for x in self.__class__.__bases__:
|
||||||
if hasattr(x, 'add_routes'):
|
if hasattr(x, 'add_routes'):
|
||||||
x.__init__(self)
|
x.__init__(self)
|
||||||
|
@ -41,7 +41,8 @@ class ContentServer(object):
|
|||||||
connect('root', '/', self.index)
|
connect('root', '/', self.index)
|
||||||
connect('old', '/old', self.old)
|
connect('old', '/old', self.old)
|
||||||
connect('get', '/get/{what}/{id}', self.get,
|
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,
|
connect('static', '/static/{name:.*?}', self.static,
|
||||||
conditions=dict(method=["GET", "HEAD"]))
|
conditions=dict(method=["GET", "HEAD"]))
|
||||||
connect('favicon', '/favicon.png', self.favicon,
|
connect('favicon', '/favicon.png', self.favicon,
|
||||||
|
@ -5,11 +5,12 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import time, sys
|
import time, sys, uuid, hashlib
|
||||||
from urllib import quote as quote_, unquote as unquote_
|
from urllib import quote as quote_, unquote as unquote_
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
from cherrypy.lib.auth_digest import digest_auth, get_ha1_dict_plain
|
||||||
|
|
||||||
from calibre import strftime as _strftime, prints, isbytestring
|
from calibre import strftime as _strftime, prints, isbytestring
|
||||||
from calibre.utils.date import now as nowf
|
from calibre.utils.date import now as nowf
|
||||||
@ -58,6 +59,53 @@ def expose(func):
|
|||||||
|
|
||||||
return do
|
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):
|
def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
|
||||||
if not hasattr(dt, 'timetuple'):
|
if not hasattr(dt, 'timetuple'):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user