From 6b43ee7fe585b95d49b4f78ea19ec82197d9874c Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Fri, 29 May 2020 13:45:25 +0000 Subject: [PATCH 01/20] Add RAR/CBR support --- shard.lock | 4 +++ shard.yml | 2 ++ src/archive.cr | 53 ++++++++++++++++++++++++++++++++++++++ src/library.cr | 32 +++++++++++------------ src/mangadex/downloader.cr | 2 +- src/util.cr | 8 ++---- 6 files changed, 77 insertions(+), 24 deletions(-) create mode 100644 src/archive.cr diff --git a/shard.lock b/shard.lock index c84ac06..86fc39f 100644 --- a/shard.lock +++ b/shard.lock @@ -4,6 +4,10 @@ shards: github: crystal-ameba/ameba version: 0.12.0 + archive: + github: hkalexling/archive.cr + commit: 64223b64d79afafcec67a127b92f21f6625ce5ce + baked_file_system: github: schovi/baked_file_system version: 0.9.8 diff --git a/shard.yml b/shard.yml index 15e8fd3..5eb5561 100644 --- a/shard.yml +++ b/shard.yml @@ -19,6 +19,8 @@ dependencies: github: crystal-lang/crystal-sqlite3 baked_file_system: github: schovi/baked_file_system + archive: + github: hkalexling/archive.cr development_dependencies: ameba: diff --git a/src/archive.cr b/src/archive.cr new file mode 100644 index 0000000..afdf143 --- /dev/null +++ b/src/archive.cr @@ -0,0 +1,53 @@ +require "zip" +require "archive" + +# A unified class to handle all supported archive formats. It uses the ::Zip +# module in crystal standard library if the target file is a zip archive. +# Otherwise it uses `archive.cr`. +class ArchiveFile + def initialize(@filename : String) + if [".cbz", ".zip"].includes? File.extname filename + @archive_file = Zip::File.new filename + else + @archive_file = Archive::File.new filename + end + end + + def self.open(filename : String, &) + s = self.new filename + yield s + s.close + end + + def close + if @archive_file.is_a? Zip::File + @archive_file.as(Zip::File).close + end + end + + # Lists all file entries + def entries + ary = [] of Zip::File::Entry | Archive::Entry + @archive_file.entries.map do |e| + if (e.is_a? Zip::File::Entry && e.file?) || + (e.is_a? Archive::Entry && e.info.file?) + ary.push e + end + end + ary + end + + def read_entry(e : Zip::File::Entry | Archive::Entry) : Bytes? + if e.is_a? Zip::File::Entry + data = nil + e.open do |io| + slice = Bytes.new e.uncompressed_size + bytes_read = io.read_fully? slice + data = slice if bytes_read + end + data + else + e.read + end + end +end diff --git a/src/library.cr b/src/library.cr index 0af92ac..2520f6c 100644 --- a/src/library.cr +++ b/src/library.cr @@ -1,8 +1,8 @@ -require "zip" require "mime" require "json" require "uri" require "./util" +require "./archive" struct Image property data : Bytes @@ -25,7 +25,7 @@ class Entry @title = File.basename path, File.extname path @encoded_title = URI.encode @title @size = (File.size path).humanize_bytes - file = Zip::File.new path + file = ArchiveFile.new path @pages = file.entries.count do |e| ["image/jpeg", "image/png"].includes? \ MIME.from_filename? e.filename @@ -68,7 +68,8 @@ class Entry end def read_page(page_num) - Zip::File.open @zip_path do |file| + img = nil + ArchiveFile.open @zip_path do |file| page = file.entries .select { |e| ["image/jpeg", "image/png"].includes? \ @@ -78,16 +79,13 @@ class Entry compare_alphanumerically a.filename, b.filename } .[page_num - 1] - page.open do |io| - slice = Bytes.new page.uncompressed_size - bytes_read = io.read_fully? slice - unless bytes_read - return nil - end - return Image.new slice, MIME.from_filename(page.filename), - page.filename, bytes_read + data = file.read_entry page + if data + img = Image.new data, MIME.from_filename(page.filename), page.filename, + data.size end end + img end end @@ -115,12 +113,12 @@ class Title @title_ids << title.id next end - if [".zip", ".cbz"].includes? File.extname path - zip_exception = validate_zip path - unless zip_exception.nil? - Logger.warn "File #{path} is corrupted or is not a valid zip " \ - "archive. Ignoring it." - Logger.debug "Zip error: #{zip_exception}" + if [".zip", ".cbz", ".rar", ".cbr"].includes? File.extname path + archive_exception = validate_archive path + unless archive_exception.nil? + Logger.warn "File #{path} is corrupted or is not a valid archive. " \ + "Ignoring it." + Logger.debug "Archive error: #{archive_exception}" next end entry = Entry.new path, self, @id, storage diff --git a/src/mangadex/downloader.cr b/src/mangadex/downloader.cr index 53c6776..6a62e59 100644 --- a/src/mangadex/downloader.cr +++ b/src/mangadex/downloader.cr @@ -371,7 +371,7 @@ module MangaDex writer.close Logger.debug "cbz File created at #{zip_path}" - zip_exception = validate_zip zip_path + zip_exception = validate_archive zip_path if !zip_exception.nil? @queue.add_message "The downloaded archive is corrupted. " \ "Error: #{zip_exception}", job diff --git a/src/util.cr b/src/util.cr index bb6cd0f..cfdd2ea 100644 --- a/src/util.cr +++ b/src/util.cr @@ -85,12 +85,8 @@ def compare_alphanumerically(a : String, b : String) compare_alphanumerically split_by_alphanumeric(a), split_by_alphanumeric(b) end -# When downloading from MangaDex, the zip/cbz file would not be valid -# before the download is completed. If we scan the zip file, -# Entry.new would throw, so we use this method to check before -# constructing Entry -def validate_zip(path : String) : Exception? - file = Zip::File.new path +def validate_archive(path : String) : Exception? + file = ArchiveFile.new path file.close return rescue e From 1efb3009882f8d2d1e3e1697a67148bd74b8722c Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Fri, 29 May 2020 14:00:59 +0000 Subject: [PATCH 02/20] Use `archive.cr` v0.1.0 --- shard.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.lock b/shard.lock index 86fc39f..74ad3e1 100644 --- a/shard.lock +++ b/shard.lock @@ -6,7 +6,7 @@ shards: archive: github: hkalexling/archive.cr - commit: 64223b64d79afafcec67a127b92f21f6625ce5ce + version: 0.1.0 baked_file_system: github: schovi/baked_file_system From 5260a82e8883b5140994befebd97c5ea75e6e22a Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Fri, 29 May 2020 14:04:17 +0000 Subject: [PATCH 03/20] Add libarchive libraries to Docker and build files --- .github/workflows/build.yml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8cad50d..a6905f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install dependencies - run: apk add --no-cache yarn yaml sqlite-static + run: apk add --no-cache yarn yaml sqlite-static libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static - name: Build run: make static - name: Linter diff --git a/Dockerfile b/Dockerfile index d763ddf..122ea76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /Mango COPY . . COPY package*.json . -RUN apk add --no-cache yarn yaml sqlite-static \ +RUN apk add --no-cache yarn yaml sqlite-static libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static \ && make static FROM library/alpine From bedcac4e3549f8cd641eeb303f295971bd1a3c89 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Fri, 29 May 2020 14:28:18 +0000 Subject: [PATCH 04/20] Add missing `libarchive-dev` library --- .github/workflows/build.yml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a6905f7..37d86c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install dependencies - run: apk add --no-cache yarn yaml sqlite-static libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static + run: apk add --no-cache yarn yaml sqlite-static libarchive-dev libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static - name: Build run: make static - name: Linter diff --git a/Dockerfile b/Dockerfile index 122ea76..6ce10b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /Mango COPY . . COPY package*.json . -RUN apk add --no-cache yarn yaml sqlite-static libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static \ +RUN apk add --no-cache yarn yaml sqlite-static libarchive-dev libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static \ && make static FROM library/alpine From 43ee8f3b857829ae8ece702d2f5db3cb8f988450 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Fri, 29 May 2020 16:23:48 +0000 Subject: [PATCH 05/20] Pass in `production` flag when installing shards --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index df5537a..4a075b5 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ static: uglify | libs crystal build src/mango.cr --release --progress --static libs: - shards install + shards install --production run: crystal run src/mango.cr --error-trace From dd01e632a27ce338b255ac060fa24404c419c63f Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Fri, 29 May 2020 16:31:47 +0000 Subject: [PATCH 06/20] Promote ameba from development dependency to regular dependency So I can use it in CI while keeping the `--production` flag in Makefile --- shard.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/shard.yml b/shard.yml index 5eb5561..e7c9e2d 100644 --- a/shard.yml +++ b/shard.yml @@ -21,7 +21,5 @@ dependencies: github: schovi/baked_file_system archive: github: hkalexling/archive.cr - -development_dependencies: ameba: github: crystal-ameba/ameba From 651bd17612ece4856e71e61df4b6b70f8a2940b6 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sat, 30 May 2020 15:14:39 +0000 Subject: [PATCH 07/20] Rewrite option parsing using clim and add the `admin` subcommand --- shard.lock | 6 +++- shard.yml | 2 ++ src/mango.cr | 87 +++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 73 insertions(+), 22 deletions(-) diff --git a/shard.lock b/shard.lock index 74ad3e1..e955617 100644 --- a/shard.lock +++ b/shard.lock @@ -2,7 +2,7 @@ version: 1.0 shards: ameba: github: crystal-ameba/ameba - version: 0.12.0 + version: 0.12.1 archive: github: hkalexling/archive.cr @@ -12,6 +12,10 @@ shards: github: schovi/baked_file_system version: 0.9.8 + clim: + github: at-grandpa/clim + version: 0.12.0 + db: github: crystal-lang/crystal-db version: 0.9.0 diff --git a/shard.yml b/shard.yml index e7c9e2d..7ec37f0 100644 --- a/shard.yml +++ b/shard.yml @@ -23,3 +23,5 @@ dependencies: github: hkalexling/archive.cr ameba: github: crystal-ameba/ameba + clim: + github: at-grandpa/clim diff --git a/src/mango.cr b/src/mango.cr index 26149a7..53a281c 100644 --- a/src/mango.cr +++ b/src/mango.cr @@ -2,31 +2,76 @@ require "./config" require "./server" require "./mangadex/*" require "option_parser" +require "clim" -VERSION = "0.4.0" +MANGO_VERSION = "0.4.0" -config_path = nil +macro common_option + option "-c PATH", "--config=PATH", type: String, + desc: "Path to the config file" +end -OptionParser.parse do |parser| - parser.banner = "Mango e-manga server/reader. Version #{VERSION}\n" +macro throw(msg) + puts "ERROR: #{{{msg}}}" + puts + puts "Please see the `--help`." + exit 1 +end - parser.on "-v", "--version", "Show version" do - puts "Version #{VERSION}" - exit - end - parser.on "-h", "--help", "Show help" do - puts parser - exit - end - parser.on "-c PATH", "--config=PATH", - "Path to the config file. " \ - "Default is `~/.config/mango/config.yml`" do |path| - config_path = path +class CLI < Clim + main do + desc "Mango - Manga Server and Web Reader. Version #{MANGO_VERSION}" + usage "mango [sub_command] [options]" + help short: "-h" + version "Version #{MANGO_VERSION}", short: "-v" + common_option + run do |opts| + Config.load(opts.config).set_current + MangaDex::Downloader.default + + server = Server.new + server.start + end + + sub "admin" do + desc "Run admin tools" + usage "mango admin [tool]" + help short: "-h" + run do |opts| + puts opts.help_string + end + sub "user" do + desc "User management tool" + usage "mango admin user [arguments] [options]" + help short: "-h" + argument "action", type: String, + desc: "Action to make. Can be add/delete/update", required: true + argument "username", type: String, + desc: "Username to update or delete" + option "-u USERNAME", "--username=USERNAME", type: String, + desc: "Username" + option "-p PASSWORD", "--password=PASSWORD", type: String, + desc: "Password" + option "--admin", desc: "Admin flag", type: Bool, default: false + common_option + run do |opts, args| + Config.load(opts.config).set_current + + case args.action + when "add" + throw "Options `-u` and `-p` required." if opts.username.nil? || + opts.password.nil? + when "delete" + throw "Argument `username` required." if args.username.nil? + when "update" + throw "Argument `username` required." if args.username.nil? + else + throw "Unknown action \"#{args.action}\"." + end + end + end + end end end -Config.load(config_path).set_current -MangaDex::Downloader.default - -server = Server.new -server.start +CLI.start(ARGV) From 8bbbe650f1ed334789445c5458183fa804b52950 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 14:25:15 +0000 Subject: [PATCH 08/20] Allow skipping initial user creation --- src/storage.cr | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/storage.cr b/src/storage.cr index e6c0ad9..82341a8 100644 --- a/src/storage.cr +++ b/src/storage.cr @@ -22,7 +22,7 @@ class Storage @@default.not_nil! end - def initialize(db_path : String? = nil) + def initialize(db_path : String? = nil, init_user = true) @path = db_path || Config.current.db_path dir = File.dirname @path unless Dir.exists? dir @@ -51,12 +51,15 @@ class Storage Logger.debug "Creating DB file at #{@path}" db.exec "create unique index username_idx on users (username)" db.exec "create unique index token_idx on users (token)" - random_pw = random_str - hash = hash_password random_pw - db.exec "insert into users values (?, ?, ?, ?)", - "admin", hash, nil, 1 - Logger.log "Initial user created. You can log in with " \ - "#{{"username" => "admin", "password" => random_pw}}" + + if init_user + random_pw = random_str + hash = hash_password random_pw + db.exec "insert into users values (?, ?, ?, ?)", + "admin", hash, nil, 1 + Logger.log "Initial user created. You can log in with " \ + "#{{"username" => "admin", "password" => random_pw}}" + end end end end From b724b4d5089d5486f42050af4d22eb5a48f8dbd2 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 14:26:20 +0000 Subject: [PATCH 09/20] Move username/password validation to `Storage` class --- src/routes/admin.cr | 31 ------------------------------- src/storage.cr | 6 +++++- src/util.cr | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/routes/admin.cr b/src/routes/admin.cr index 1fbd978..c2f8fa9 100644 --- a/src/routes/admin.cr +++ b/src/routes/admin.cr @@ -32,20 +32,6 @@ class AdminRouter < Router # would not contain `admin` admin = !env.params.body["admin"]?.nil? - if username.size < 3 - raise "Username should contain at least 3 characters" - end - if (username =~ /^[A-Za-z0-9_]+$/).nil? - raise "Username should contain alphanumeric characters " \ - "and underscores only" - end - if password.size < 6 - raise "Password should contain at least 6 characters" - end - if (password =~ /^[[:ascii:]]+$/).nil? - raise "password should contain ASCII characters only" - end - @context.storage.new_user username, password, admin redirect env, "/admin/user" @@ -65,23 +51,6 @@ class AdminRouter < Router admin = !env.params.body["admin"]?.nil? original_username = env.params.url["original_username"] - if username.size < 3 - raise "Username should contain at least 3 characters" - end - if (username =~ /^[A-Za-z0-9_]+$/).nil? - raise "Username should contain alphanumeric characters " \ - "and underscores only" - end - - if password.size != 0 - if password.size < 6 - raise "Password should contain at least 6 characters" - end - if (password =~ /^[[:ascii:]]+$/).nil? - raise "password should contain ASCII characters only" - end - end - @context.storage.update_user \ original_username, username, password, admin diff --git a/src/storage.cr b/src/storage.cr index 82341a8..f577557 100644 --- a/src/storage.cr +++ b/src/storage.cr @@ -127,6 +127,8 @@ class Storage end def new_user(username, password, admin) + validate_username username + validate_password password admin = (admin ? 1 : 0) DB.open "sqlite3://#{@path}" do |db| hash = hash_password password @@ -137,8 +139,10 @@ class Storage def update_user(original_username, username, password, admin) admin = (admin ? 1 : 0) + validate_username username + validate_password password unless password.empty? DB.open "sqlite3://#{@path}" do |db| - if password.size == 0 + if password.empty? db.exec "update users set username = (?), admin = (?) " \ "where username = (?)", username, admin, original_username diff --git a/src/util.cr b/src/util.cr index cfdd2ea..767f79e 100644 --- a/src/util.cr +++ b/src/util.cr @@ -101,3 +101,22 @@ def redirect(env, path) base = Config.current.base_url env.redirect File.join base, path end + +def validate_username(username) + if username.size < 3 + raise "Username should contain at least 3 characters" + end + if (username =~ /^[A-Za-z0-9_]+$/).nil? + raise "Username should contain alphanumeric characters " \ + "and underscores only" + end +end + +def validate_password(password) + if password.size < 6 + raise "Password should contain at least 6 characters" + end + if (password =~ /^[[:ascii:]]+$/).nil? + raise "password should contain ASCII characters only" + end +end From d72d635c6804702681e6f6905a79a52b9ca80b9a Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 14:30:45 +0000 Subject: [PATCH 10/20] Add `admin/user` sub-command --- src/mango.cr | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/mango.cr b/src/mango.cr index 53a281c..7bd0829 100644 --- a/src/mango.cr +++ b/src/mango.cr @@ -45,26 +45,50 @@ class CLI < Clim usage "mango admin user [arguments] [options]" help short: "-h" argument "action", type: String, - desc: "Action to make. Can be add/delete/update", required: true + desc: "Action to perform. Can be add/delete/update/list" argument "username", type: String, desc: "Username to update or delete" option "-u USERNAME", "--username=USERNAME", type: String, desc: "Username" option "-p PASSWORD", "--password=PASSWORD", type: String, desc: "Password" - option "--admin", desc: "Admin flag", type: Bool, default: false + option "-a", "--admin", desc: "Admin flag", type: Bool, default: false common_option run do |opts, args| Config.load(opts.config).set_current + storage = Storage.new nil, false case args.action when "add" throw "Options `-u` and `-p` required." if opts.username.nil? || opts.password.nil? + storage.new_user opts.username.not_nil!, + opts.password.not_nil!, opts.admin when "delete" throw "Argument `username` required." if args.username.nil? + storage.delete_user args.username when "update" throw "Argument `username` required." if args.username.nil? + username = opts.username || args.username + password = opts.password || "" + storage.update_user args.username, username.not_nil!, + password.not_nil!, opts.admin + when "list" + users = storage.list_users + name_length = users.map(&.[0].size).max + l_cell_width = ["username".size, name_length].max + r_cell_width = "admin access".size + header = " #{"username".ljust l_cell_width} | admin access " + puts "-" * header.size + puts header + puts "-" * header.size + users.each do |name, admin| + puts " #{name.ljust l_cell_width} | " \ + "#{admin.to_s.ljust r_cell_width} " + end + puts "-" * header.size + when nil + puts opts.help_string else throw "Unknown action \"#{args.action}\"." end From 4371c7877dfd3c0ac32bdecd34240b773a508212 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 14:34:42 +0000 Subject: [PATCH 11/20] Use base URL in cookies path --- src/routes/main.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/main.cr b/src/routes/main.cr index e739d82..7b05fd1 100644 --- a/src/routes/main.cr +++ b/src/routes/main.cr @@ -25,6 +25,7 @@ class MainRouter < Router token = @context.storage.verify_user(username, password).not_nil! cookie = HTTP::Cookie.new "token", token + cookie.path = Config.current.base_url cookie.expires = Time.local.shift years: 1 env.response.cookies << cookie redirect env, "/" From bcb95d14623f1ddd8a836ce0c9616cec8fe14056 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 15:14:17 +0000 Subject: [PATCH 12/20] Make `validate_archive` more thorough --- shard.lock | 2 +- src/archive.cr | 6 ++++++ src/util.cr | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/shard.lock b/shard.lock index 74ad3e1..a58d6b6 100644 --- a/shard.lock +++ b/shard.lock @@ -6,7 +6,7 @@ shards: archive: github: hkalexling/archive.cr - version: 0.1.0 + version: 0.2.0 baked_file_system: github: schovi/baked_file_system diff --git a/src/archive.cr b/src/archive.cr index afdf143..29dedfb 100644 --- a/src/archive.cr +++ b/src/archive.cr @@ -50,4 +50,10 @@ class ArchiveFile e.read end end + + def check + if @archive_file.is_a? Archive::File + @archive_file.as(Archive::File).check + end + end end diff --git a/src/util.cr b/src/util.cr index cfdd2ea..0d6a993 100644 --- a/src/util.cr +++ b/src/util.cr @@ -87,6 +87,7 @@ end def validate_archive(path : String) : Exception? file = ArchiveFile.new path + file.check file.close return rescue e From 27dab3c989968e84ce7bf299f57c42c7a87f9597 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 15:26:11 +0000 Subject: [PATCH 13/20] Disable initial user creation in spec --- spec/spec_helper.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index a8bc5ab..578ae42 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -45,7 +45,7 @@ end def with_storage with_default_config do temp_db = get_tempfile "mango-test-db" - storage = Storage.new temp_db.path + storage = Storage.new temp_db.path, false clear = yield storage, temp_db.path if clear == true temp_db.delete From 1b9d83f367a43da9f0b21daa11e7e2ebd559a9f6 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Mon, 1 Jun 2020 04:54:28 +0000 Subject: [PATCH 14/20] Report if archive is not readable #49 --- src/library.cr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/library.cr b/src/library.cr index 2520f6c..6a877a8 100644 --- a/src/library.cr +++ b/src/library.cr @@ -114,6 +114,11 @@ class Title next end if [".zip", ".cbz", ".rar", ".cbr"].includes? File.extname path + unless File.readable? path + Logger.warn "File #{path} is not readable. Please make sure the " \ + "file permission is configured correctly." + next + end archive_exception = validate_archive path unless archive_exception.nil? Logger.warn "File #{path} is corrupted or is not a valid archive. " \ From 9b5aea223d28a66f7ceb31b9b208c27ebaf6f64f Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Mon, 1 Jun 2020 13:38:15 +0000 Subject: [PATCH 15/20] Promote archive error log level to warning --- src/library.cr | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/library.cr b/src/library.cr index 6a877a8..935e7ee 100644 --- a/src/library.cr +++ b/src/library.cr @@ -121,9 +121,8 @@ class Title end archive_exception = validate_archive path unless archive_exception.nil? - Logger.warn "File #{path} is corrupted or is not a valid archive. " \ - "Ignoring it." - Logger.debug "Archive error: #{archive_exception}" + Logger.warn "Unable to extract archive #{path}. Ignoring it. " \ + "Archive error: #{archive_exception}" next end entry = Entry.new path, self, @id, storage From e214e00dfb2e13bc1be9300f08ac819bd37f3fa9 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Mon, 1 Jun 2020 13:50:51 +0000 Subject: [PATCH 16/20] Include port number in token --- src/handlers/auth_handler.cr | 4 +++- src/routes/main.cr | 6 ++++-- src/util.cr | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/handlers/auth_handler.cr b/src/handlers/auth_handler.cr index afa17df..a64c83d 100644 --- a/src/handlers/auth_handler.cr +++ b/src/handlers/auth_handler.cr @@ -9,7 +9,9 @@ class AuthHandler < Kemal::Handler def call(env) return call_next(env) if request_path_startswith env, ["/login", "/logout"] - cookie = env.request.cookies.find { |c| c.name == "token" } + cookie = env.request.cookies.find do |c| + c.name == "token-#{Config.current.port}" + end if cookie.nil? || !@storage.verify_token cookie.value return redirect env, "/login" end diff --git a/src/routes/main.cr b/src/routes/main.cr index 7b05fd1..3dc4b40 100644 --- a/src/routes/main.cr +++ b/src/routes/main.cr @@ -9,7 +9,9 @@ class MainRouter < Router get "/logout" do |env| begin - cookie = env.request.cookies.find { |c| c.name == "token" }.not_nil! + cookie = env.request.cookies.find do |c| + c.name == "token-#{Config.current.port}" + end.not_nil! @context.storage.logout cookie.value rescue e @context.error "Error when attempting to log out: #{e}" @@ -24,7 +26,7 @@ class MainRouter < Router password = env.params.body["password"] token = @context.storage.verify_user(username, password).not_nil! - cookie = HTTP::Cookie.new "token", token + cookie = HTTP::Cookie.new "token-#{Config.current.port}", token cookie.path = Config.current.base_url cookie.expires = Time.local.shift years: 1 env.response.cookies << cookie diff --git a/src/util.cr b/src/util.cr index 36a7791..5146fde 100644 --- a/src/util.cr +++ b/src/util.cr @@ -6,7 +6,9 @@ UPLOAD_URL_PREFIX = "/uploads" macro layout(name) base_url = Config.current.base_url begin - cookie = env.request.cookies.find { |c| c.name == "token" } + cookie = env.request.cookies.find do |c| + c.name == "token-#{Config.current.port}" + end is_admin = false unless cookie.nil? is_admin = @context.storage.verify_admin cookie.value @@ -26,7 +28,9 @@ end macro get_username(env) # if the request gets here, it has gone through the auth handler, and # we can be sure that a valid token exists, so we can use not_nil! here - cookie = {{env}}.request.cookies.find { |c| c.name == "token" }.not_nil! + cookie = {{env}}.request.cookies.find do |c| + c.name == "token-#{Config.current.port}" + end.not_nil! (@context.storage.verify_token cookie.value).not_nil! end From 06fe2ccf1684ab10088245b4da5c977812168226 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Tue, 2 Jun 2020 15:07:17 +0000 Subject: [PATCH 17/20] Handle escaped characters when filtering (#51) [skip ci] --- public/js/download.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/public/js/download.js b/public/js/download.js index ab28fa9..5c848d7 100644 --- a/public/js/download.js +++ b/public/js/download.js @@ -242,7 +242,10 @@ const buildTable = () => { Object.entries(filters).forEach(([k, v]) => { if (v === 'All') return; if (k === 'group') { - chapters = chapters.filter(c => v in c.groups); + chapters = chapters.filter(c => { + unescaped_groups = Object.entries(c.groups).map(([g, id]) => unescapeHTML(g)); + return unescaped_groups.indexOf(v) >= 0; + }); return; } if (k === 'lang') { @@ -297,3 +300,9 @@ const buildTable = () => { }); $('#selection-controls').removeAttr('hidden'); }; + +const unescapeHTML = (str) => { + var elt = document.createElement("span"); + elt.innerHTML = str; + return elt.innerText; +}; From f9a2534f80cf77a27b872c916e0f609fecd49731 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Tue, 2 Jun 2020 15:21:08 +0000 Subject: [PATCH 18/20] Mention CBR support in README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 88a984a..e3a10c2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Mango is a self-hosted manga server and reader. Its features include - Multi-user support - Dark/light mode switch -- Supports both `.zip` and `.cbz` formats +- Supported formats: `.cbz`, `.zip`, `.cbr` and `.rar` - Supports nested folders in library - Automatically stores reading progress - Built-in [MangaDex](https://mangadex.org/) downloader @@ -35,7 +35,7 @@ Simply download the pre-built binary file `mango` for the latest [release](https ### Docker (via Dockerhub) -The official docker images are available on [Dockerhub](https://hub.docker.com/r/hkalexling/mango). +The official docker images are available on [Dockerhub](https://hub.docker.com/r/hkalexling/mango). ### Build from source @@ -85,7 +85,7 @@ mangadex: ### Library Structure -You can organize your `.cbz/.zip` files in nested folders in the library directory. Here's an example: +You can organize your archive files in nested folders in the library directory. Here's an example: ``` . @@ -97,8 +97,8 @@ You can organize your `.cbz/.zip` files in nested folders in the library directo └── Manga 2    └── Vol. 1    └── Ch.1 - Ch.3 -    ├── 1.zip -    ├── 2.zip +    ├── 1.zip +    ├── 2.zip    └── 3.zip ``` From 160a249dc6e74721a4eb98a0746c643ea4cd1b52 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Tue, 2 Jun 2020 15:26:38 +0000 Subject: [PATCH 19/20] Update CLI help message in README --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e3a10c2..f768343 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ The official docker images are available on [Dockerhub](https://hub.docker.com/r ### Build from source -1. Make sure you have `crystal`, `shards` and `yarn` installed. You might also need to install the development headers for `libsqlite3` and `libyaml`. +1. Make sure you have `crystal`, `shards` and `yarn` installed. You might also need to install the development headers of some libraries. Please see the [Dockerfile](https://github.com/hkalexling/Mango/blob/master/Dockerfile) for the full list of dependencies 2. Clone the repository 3. `make && sudo make install` 4. Start Mango by running the command `mango` @@ -50,11 +50,21 @@ The official docker images are available on [Dockerhub](https://hub.docker.com/r ### CLI ``` -Mango e-manga server/reader. Version 0.4.0 + Mango - Manga Server and Web Reader. Version 0.4.0 - -v, --version Show version - -h, --help Show help - -c PATH, --config=PATH Path to the config file. Default is `~/.config/mango/config.yml` + Usage: + + mango [sub_command] [options] + + Options: + + -c PATH, --config=PATH Path to the config file [type:String] + -h, --help Show this help. + -v, --version Show version. + + Sub Commands: + + admin Run admin tools ``` ### Config From a72dfcecd3fa3b79fdd708dcb12a091068b772d9 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Tue, 2 Jun 2020 15:29:32 +0000 Subject: [PATCH 20/20] Bump version to v0.5.0 --- README.md | 2 +- shard.yml | 2 +- src/mango.cr | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f768343..0cdc080 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ The official docker images are available on [Dockerhub](https://hub.docker.com/r ### CLI ``` - Mango - Manga Server and Web Reader. Version 0.4.0 + Mango - Manga Server and Web Reader. Version 0.5.0 Usage: diff --git a/shard.yml b/shard.yml index 7ec37f0..52ad045 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: mango -version: 0.4.0 +version: 0.5.0 authors: - Alex Ling diff --git a/src/mango.cr b/src/mango.cr index 7bd0829..592d0ec 100644 --- a/src/mango.cr +++ b/src/mango.cr @@ -4,7 +4,7 @@ require "./mangadex/*" require "option_parser" require "clim" -MANGO_VERSION = "0.4.0" +MANGO_VERSION = "0.5.0" macro common_option option "-c PATH", "--config=PATH", type: String,