From 2fe82fc41585aa7c42c8b1edf055721f1be9f346 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Thu, 25 Dec 2025 22:44:52 +0800 Subject: [PATCH] Allow specifying custom header values when fetching rule list --- omega-locales/en_US/LC_MESSAGES/omega-web.po | 24 +++++++++++++++++ .../src/module/fetch_url.coffee | 15 ++++++----- omega-target/src/options.coffee | 10 +++++-- .../omega/controllers/switch_profile.coffee | 8 ++++++ omega-web/src/partials/profile_switch.jade | 26 ++++++++++++++++++- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/omega-locales/en_US/LC_MESSAGES/omega-web.po b/omega-locales/en_US/LC_MESSAGES/omega-web.po index 27163a725..79cff2390 100644 --- a/omega-locales/en_US/LC_MESSAGES/omega-web.po +++ b/omega-locales/en_US/LC_MESSAGES/omega-web.po @@ -694,6 +694,30 @@ msgstr "" "The rule list will be updated from this URL. If it is left blank, the " "following text will be parsed instead." +msgid "options_group_ruleListHeaders" +msgstr "Custom Headers" + +msgid "options_ruleListHeaderName" +msgstr "Header Name" + +msgid "options_ruleListHeaderValue" +msgstr "Header Value" + +msgid "options_ruleListHeaderNamePlaceholder" +msgstr "e.g. Authorization" + +msgid "options_ruleListHeaderValuePlaceholder" +msgstr "e.g. Bearer my-token" + +msgid "options_ruleListHeadersHelp" +msgstr "You can specify custom HTTP headers to include when downloading the rule list. This is useful if, for example, the source requires authentication." + +msgid "options_deleteHeader" +msgstr "Delete header" + +msgid "options_addHeader" +msgstr "Add header" + msgid "options_group_ruleListText" msgstr "Rule List Text" diff --git a/omega-target-chromium-extension/src/module/fetch_url.coffee b/omega-target-chromium-extension/src/module/fetch_url.coffee index 212d6817f..c093108f3 100644 --- a/omega-target-chromium-extension/src/module/fetch_url.coffee +++ b/omega-target-chromium-extension/src/module/fetch_url.coffee @@ -3,8 +3,11 @@ Promise = OmegaTarget.Promise Url = require('url') ContentTypeRejectedError = OmegaTarget.ContentTypeRejectedError -xhrWrapper = (args...) -> - fetch(args...).then((response) -> +xhrWrapper = (url, headers) -> + options = {} + if headers + options.headers = headers + fetch(url, options).then((response) -> response.text().then((body) -> return [response, body] ) @@ -18,7 +21,7 @@ xhrWrapper = (args...) -> throw new OmegaTarget.HttpServerError(err) throw new OmegaTarget.HttpError(err) -fetchUrl = (dest_url, opt_bypass_cache, opt_type_hints) -> +fetchUrl = (dest_url, headers, opt_bypass_cache, opt_type_hints) -> getResBody = ([response, body]) -> return body unless opt_type_hints contentType = response.headers['content-type']?.toLowerCase() @@ -36,11 +39,11 @@ fetchUrl = (dest_url, opt_bypass_cache, opt_type_hints) -> parsed.query['_'] = Date.now() dest_url_nocache = Url.format(parsed) # Try first with the dumb parameter to bypass cache. - xhrWrapper(dest_url_nocache).then(getResBody).catch -> + xhrWrapper(dest_url_nocache, headers).then(getResBody).catch -> # If failed, try again with the original URL. - xhrWrapper(dest_url).then(getResBody) + xhrWrapper(dest_url, headers).then(getResBody) else - xhrWrapper(dest_url).then(getResBody) + xhrWrapper(dest_url, headers).then(getResBody) defaultHintHandler = (response, body, {contentType, hint}) -> if '!' + contentType == hint diff --git a/omega-target/src/options.coffee b/omega-target/src/options.coffee index c56ef5a62..bafc4610c 100644 --- a/omega-target/src/options.coffee +++ b/omega-target/src/options.coffee @@ -778,7 +778,12 @@ class Options url = OmegaPac.Profiles.updateUrl(profile) if url type_hints = OmegaPac.Profiles.updateContentTypeHints(profile) - fetchResult = @fetchUrl(url, opt_bypass_cache, type_hints) + headers = {} + if profile.headers + for header in profile.headers + # Being defensive here: filter out headers with empty names + headers[header.name] = header.value if header.name + fetchResult = @fetchUrl(url, headers, opt_bypass_cache, type_hints) results[key] = fetchResult.then((data) => # Errors and unsuccessful response codes shoud have been already # rejected by fetchUrl and will not end up here. @@ -805,11 +810,12 @@ class Options # Make an HTTP GET request to fetch the content of the url. # In base class, this method is not implemented and will always reject. # @param {string} url The name of the profiles, + # @param {?{}} headers Optional headers dictionary with name-value pairs # @param {?bool} opt_bypass_cache Do not read from the cache if true # @param {?string} opt_type_hints MIME type hints for downloaded content. # @returns {Promise} The text content fetched from the url ### - fetchUrl: (url, opt_bypass_cache, opt_type_hints) -> + fetchUrl: (url, headers, opt_bypass_cache, opt_type_hints) -> Promise.reject new Error('not implemented') _replaceRefChanges: (fromName, toName, changes) -> diff --git a/omega-web/src/omega/controllers/switch_profile.coffee b/omega-web/src/omega/controllers/switch_profile.coffee index 3776e1d12..5cea8b587 100644 --- a/omega-web/src/omega/controllers/switch_profile.coffee +++ b/omega-web/src/omega/controllers/switch_profile.coffee @@ -458,3 +458,11 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $rootScope, omegaTarget.state('web.switchGuide', 'shown') return if $scope.profile.rules.length == 0 $script 'js/switch_profile_guide.js' + + # == Custom Headers == + $scope.addHeader = -> + $scope.attached.headers ?= [] + $scope.attached.headers.push({name: '', value: ''}) + + $scope.removeHeader = (index) -> + $scope.attached.headers.splice(index, 1) diff --git a/omega-web/src/partials/profile_switch.jade b/omega-web/src/partials/profile_switch.jade index 007b227d9..8a596c075 100644 --- a/omega-web/src/partials/profile_switch.jade +++ b/omega-web/src/partials/profile_switch.jade @@ -170,7 +170,31 @@ div(ng-controller='SwitchProfileCtrl') label {{'options_group_ruleListUrl' | tr}} .width-limit.inline-form-control(input-group-clear type='url' model='attached.sourceUrl' style='vertical-align: middle') - p.help-block {{'options_ruleListUrlHelp' | tr}} + p.help-block {{'options_ruleListUrlHelp' | tr}} + .form-group + label {{'options_group_ruleListHeaders' | tr}} + .width-limit + .table-responsive(ng-if='attached.headers && attached.headers.length > 0') + table.table.table-bordered.table-condensed + thead + tr + th {{'options_ruleListHeaderName' | tr}} + th {{'options_ruleListHeaderValue' | tr}} + th {{'options_conditionActions' | tr}} + tbody + tr(ng-repeat='header in attached.headers track by $index') + td + input.form-control(type='text' ng-model='header.name' placeholder="{{'options_ruleListHeaderNamePlaceholder' | tr}}" required) + td + input.form-control(type='text' ng-model='header.value' placeholder="{{'options_ruleListHeaderValuePlaceholder' | tr}}") + td + button.btn.btn-danger.btn-sm(type='button' title="{{'options_deleteHeader' | tr}}" ng-click='removeHeader($index)') + span.glyphicon.glyphicon-trash + button.btn.btn-default.btn-sm(type='button' ng-click='addHeader()') + span.glyphicon.glyphicon-plus + = ' ' + | {{'options_addHeader' | tr}} + p.help-block {{'options_ruleListHeadersHelp' | tr}} p button.btn.btn-default(ng-disabled='!attached.sourceUrl' ng-click='updateProfile(attached.name)' ladda='updatingProfile[attached.name]' data-spinner-color="#000000"