mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-04 11:55:30 -04:00
Merge branch 'feature/opds' into dev
This commit is contained in:
commit
373ff6520a
@ -3,25 +3,97 @@ require "../storage"
|
||||
require "../util"
|
||||
|
||||
class AuthHandler < Kemal::Handler
|
||||
# Some of the code is copied form kemalcr/kemal-basic-auth on GitHub
|
||||
|
||||
BASIC = "Basic"
|
||||
AUTH = "Authorization"
|
||||
AUTH_MESSAGE = "Could not verify your access level for that URL.\n" \
|
||||
"You have to login with proper credentials"
|
||||
HEADER_LOGIN_REQUIRED = "Basic realm=\"Login Required\""
|
||||
|
||||
def initialize(@storage : Storage)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
return call_next(env) if request_path_startswith env, ["/login", "/logout"]
|
||||
def require_basic_auth(env)
|
||||
headers = HTTP::Headers.new
|
||||
env.response.status_code = 401
|
||||
env.response.headers["WWW-Authenticate"] = HEADER_LOGIN_REQUIRED
|
||||
env.response.print AUTH_MESSAGE
|
||||
call_next env
|
||||
end
|
||||
|
||||
def validate_cookie_token(env)
|
||||
cookie = env.request.cookies.find do |c|
|
||||
c.name == "token-#{Config.current.port}"
|
||||
end
|
||||
if cookie.nil? || !@storage.verify_token cookie.value
|
||||
!cookie.nil? && @storage.verify_token cookie.value
|
||||
end
|
||||
|
||||
def validate_cookie_token_admin(env)
|
||||
cookie = env.request.cookies.find do |c|
|
||||
c.name == "token-#{Config.current.port}"
|
||||
end
|
||||
!cookie.nil? && @storage.verify_admin cookie.value
|
||||
end
|
||||
|
||||
def validate_auth_header(env)
|
||||
if env.request.headers[AUTH]?
|
||||
if value = env.request.headers[AUTH]
|
||||
if value.size > 0 && value.starts_with?(BASIC)
|
||||
token = verify_user value
|
||||
return false if token.nil?
|
||||
|
||||
# TODO use port number in token key
|
||||
cookie = HTTP::Cookie.new "token", token
|
||||
cookie.path = Config.current.base_url
|
||||
cookie.expires = Time.local.shift years: 1
|
||||
env.response.cookies << cookie
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def verify_user(value)
|
||||
username, password = Base64.decode_string(value[BASIC.size + 1..-1])
|
||||
.split(":")
|
||||
@storage.verify_user username, password
|
||||
end
|
||||
|
||||
def handle_opds_auth(env)
|
||||
if validate_cookie_token(env) || validate_auth_header(env)
|
||||
return call_next env
|
||||
else
|
||||
headers = HTTP::Headers.new
|
||||
env.response.status_code = 401
|
||||
env.response.headers["WWW-Authenticate"] = HEADER_LOGIN_REQUIRED
|
||||
env.response.print AUTH_MESSAGE
|
||||
end
|
||||
end
|
||||
|
||||
def handle_auth(env)
|
||||
return call_next(env) if request_path_startswith env, ["/login", "/logout"]
|
||||
|
||||
unless validate_cookie_token env
|
||||
return redirect env, "/login"
|
||||
end
|
||||
|
||||
if request_path_startswith env, ["/admin", "/api/admin", "/download"]
|
||||
unless @storage.verify_admin cookie.value
|
||||
unless validate_cookie_token_admin env
|
||||
env.response.status_code = 403
|
||||
end
|
||||
end
|
||||
|
||||
call_next env
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if request_path_startswith env, ["/opds"]
|
||||
handle_opds_auth env
|
||||
else
|
||||
handle_auth env
|
||||
end
|
||||
end
|
||||
end
|
||||
|
32
src/routes/opds.cr
Normal file
32
src/routes/opds.cr
Normal file
@ -0,0 +1,32 @@
|
||||
require "./router"
|
||||
|
||||
class OPDSRouter < Router
|
||||
def initialize
|
||||
get "/opds" do |env|
|
||||
titles = @context.library.titles
|
||||
render_xml "src/views/opds/index.ecr"
|
||||
end
|
||||
|
||||
get "/opds/book/:title_id" do |env|
|
||||
begin
|
||||
title = @context.library.get_title(env.params.url["title_id"]).not_nil!
|
||||
render_xml "src/views/opds/title.ecr"
|
||||
rescue e
|
||||
@context.error e
|
||||
env.response.status_code = 404
|
||||
end
|
||||
end
|
||||
|
||||
get "/opds/download/:title/:entry" do |env|
|
||||
begin
|
||||
title = (@context.library.get_title env.params.url["title"]).not_nil!
|
||||
entry = (title.get_entry env.params.url["entry"]).not_nil!
|
||||
|
||||
send_attachment env, entry.zip_path
|
||||
rescue e
|
||||
@context.error e
|
||||
env.response.status_code = 404
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -53,6 +53,7 @@ class Server
|
||||
AdminRouter.new
|
||||
ReaderRouter.new
|
||||
APIRouter.new
|
||||
OPDSRouter.new
|
||||
|
||||
Kemal.config.logging = false
|
||||
add_handler LogHandler.new
|
||||
|
10
src/util.cr
10
src/util.cr
@ -39,6 +39,12 @@ def send_json(env, json)
|
||||
env.response.print json
|
||||
end
|
||||
|
||||
def send_attachment(env, path)
|
||||
MIME.register ".cbz", "application/vnd.comicbook+zip"
|
||||
MIME.register ".cbr", "application/vnd.comicbook-rar"
|
||||
send_file env, path, filename: File.basename(path), disposition: "attachment"
|
||||
end
|
||||
|
||||
def hash_to_query(hash)
|
||||
hash.map { |k, v| "#{k}=#{v}" }.join("&")
|
||||
end
|
||||
@ -125,3 +131,7 @@ def validate_password(password)
|
||||
raise "password should contain ASCII characters only"
|
||||
end
|
||||
end
|
||||
|
||||
macro render_xml(path)
|
||||
send_file env, ECR.render({{path}}).to_slice, "application/xml"
|
||||
end
|
||||
|
24
src/views/opds/index.ecr
Normal file
24
src/views/opds/index.ecr
Normal file
@ -0,0 +1,24 @@
|
||||
<!--TODO: respect base URL-->
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<id>urn:mango:index</id>
|
||||
|
||||
<link rel="self" href="/opds/" type="application/atom+xml;profile=opds-catalog;kind=navigation" />
|
||||
<link rel="start" href="/opds/" type="application/atom+xml;profile=opds-catalog;kind=navigation" />
|
||||
|
||||
<title>Library</title>
|
||||
|
||||
<author>
|
||||
<name>Mango</name>
|
||||
<uri>https://github.com/hkalexling/Mango</uri>
|
||||
</author>
|
||||
|
||||
<% titles.each do |t| %>
|
||||
<entry>
|
||||
<title><%= t.display_name %></title>
|
||||
<id>urn:mango:<%= t.id %></id>
|
||||
<link type="application/atom+xml;profile=opds-catalog;kind=navigation" rel="subsection" href="/opds/book/<%= t.id %>" />
|
||||
</entry>
|
||||
<% end %>
|
||||
</feed>
|
38
src/views/opds/title.ecr
Normal file
38
src/views/opds/title.ecr
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<id>urn:mango:<%= title.id %></id>
|
||||
|
||||
<link rel="self" href="/opds/book/<%= title.id %>" type="application/atom+xml;profile=opds-catalog;kind=navigation" />
|
||||
<link rel="start" href="/opds/" type="application/atom+xml;profile=opds-catalog;kind=navigation" />
|
||||
|
||||
<title><%= title.display_name %></title>
|
||||
|
||||
<author>
|
||||
<name>Mango</name>
|
||||
<uri>https://github.com/hkalexling/Mango</uri>
|
||||
</author>
|
||||
|
||||
<% title.titles.each do |t| %>
|
||||
<entry>
|
||||
<title><%= t.display_name %></title>
|
||||
<id>urn:mango:<%= t.id %></id>
|
||||
<link type="application/atom+xml;profile=opds-catalog;kind=navigation" rel="subsection" href="/opds/book/<%= t.id %>" />
|
||||
</entry>
|
||||
<% end %>
|
||||
|
||||
<% title.entries.each do |e| %>
|
||||
<entry>
|
||||
<title><%= e.display_name %></title>
|
||||
<id>urn:mango:<%= e.id %></id>
|
||||
|
||||
<link rel="http://opds-spec.org/image" href="<%= e.cover_url %>" />
|
||||
<link rel="http://opds-spec.org/image/thumbnail" href="<%= e.cover_url %>" />
|
||||
|
||||
<link rel="http://opds-spec.org/acquisition" href="/opds/download/<%= e.title_id %>/<%= e.id %>" title="Read" type="<%= MIME.from_filename e.zip_path %>" />
|
||||
|
||||
<link type="text/html" rel="alternate" title="Read in Mango" href="/reader/<%= e.title_id %>/<%= e.id %>" />
|
||||
<link type="text/html" rel="alternate" title="Open in Mango" href="/book/<%= e.title_id %>" />
|
||||
</entry>
|
||||
<% end %>
|
||||
|
||||
</feed>
|
Loading…
x
Reference in New Issue
Block a user