From fa91c6d6beba32deebfb4951efbb9688b42030e3 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 25 Aug 2026 15:12:19 +0300 Subject: [PATCH 01/20] fix mobile filters --- app/views/submissions_archived/index.html.erb | 2 +- .../_applied_filters.html.erb | 23 +++++++++++-------- .../index.html.erb | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/views/submissions_archived/index.html.erb b/app/views/submissions_archived/index.html.erb index a51b8099..a9ca204e 100644 --- a/app/views/submissions_archived/index.html.erb +++ b/app/views/submissions_archived/index.html.erb @@ -17,7 +17,7 @@
- <%= render 'submissions_filters/applied_filters', filter_params:, with_status: true %> + <%= render 'submissions_filters/applied_filters', filter_params:, with_status: true, with_default_status: true %> <%= render 'submissions_filters/filter_button', filter_params: %>
diff --git a/app/views/submissions_filters/_applied_filters.html.erb b/app/views/submissions_filters/_applied_filters.html.erb index 35323184..d3609361 100644 --- a/app/views/submissions_filters/_applied_filters.html.erb +++ b/app/views/submissions_filters/_applied_filters.html.erb @@ -3,15 +3,16 @@ <% ordered_filters = request.query_parameters.keys.filter_map { |key| filter_names.find { |name| key.start_with?(name) } }.uniq %> <% chip_order = ordered_filters.reverse.each_with_index.to_h { |name, index| [name, index + 1] } %> <% chip_order.default = ordered_filters.size + 1 %> -<% status_icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit', 'sent' => 'send', 'opened' => 'mail_opened' }[params[:status]] %> <% status_icons = { 'all' => 'list', 'pending' => 'clock', 'completed' => 'circle_check' } %> <% current_status = status_icons.key?(params[:status].to_s) ? params[:status].to_s : 'all' %> +<% status_icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit', 'sent' => 'send', 'opened' => 'mail_opened' }[params[:status]] %> +<% default_status_icon = status_icons[params[:status]] if status_icon.blank? && local_assigns[:with_default_status] && current_status != 'all' %> <% with_status_button = local_assigns[:with_status] && status_icon.blank? %> -<% chips_html = capture do %> - <% if status_icon %> -
+<% status_chip_html = capture do %> + <% if status_icon || default_status_icon %> +
<%= link_to submissions_filter_path('status', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %> - <%= svg_icon(status_icon, class: 'w-5 h-5 shrink-0') %> + <%= svg_icon(status_icon || default_status_icon, class: 'w-5 h-5 shrink-0') %> <%= t(params[:status]) %> <% end %> <%= link_to url_for(params: request.query_parameters.except('status')), class: 'rounded-lg ml-1 shrink-0 hover:bg-base-content hover:text-white' do %> @@ -19,6 +20,8 @@ <% end %>
<% end %> +<% end %> +<% chips_html = capture do %> <% if params[:folder].present? %>
<%= link_to submissions_filter_path('folder', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 flex-1 min-w-0 pr-1' do %> @@ -78,7 +81,8 @@
<% end %> <% end %> -<% if with_status_button && chips_html.blank? %> +<% with_status_dropdown = with_status_button && chips_html.blank? %> +<% if with_status_dropdown %> <% end %> -<% if chips_html.present? %> - - <% if with_status_button %> +<% if chips_html.present? || status_chip_html.present? %> + + <% if with_status_button && !with_status_dropdown %> <%= svg_icon(status_icons[current_status], class: 'w-5 h-5 shrink-0') %> <%= t(current_status) %> <%= svg_icon('chevron_down', class: 'w-4 h-4 shrink-0') %> <% end %> + <%= status_chip_html %> <%= chips_html %> <% end %> diff --git a/app/views/templates_archived_submissions/index.html.erb b/app/views/templates_archived_submissions/index.html.erb index 4bbf24c9..407432c0 100644 --- a/app/views/templates_archived_submissions/index.html.erb +++ b/app/views/templates_archived_submissions/index.html.erb @@ -19,7 +19,7 @@
- <%= render 'submissions_filters/applied_filters', filter_params:, with_status: true %> + <%= render 'submissions_filters/applied_filters', filter_params:, with_status: true, with_default_status: true %>
- <% if !template.archived_at? && can?(:destroy, template) %> - <%= button_to button_title(title: t('archive'), disabled_with: t('archiving')[..-4], title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete %> + <% if can?(:destroy, template) %> + <% if template.archived_at? %> + <%= button_to button_title(title: t('restore'), disabled_with: t('restoring')[..-4], icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1' %> + <% else %> + <%= button_to button_title(title: t('archive'), disabled_with: t('archiving')[..-4], title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete %> + <% end %> <% end %> <% if can?(:create, current_account.templates.new(author: current_user)) %>
@@ -99,9 +103,6 @@ <% end %> <% end %> <% if template.archived_at? %> - <% if can?(:destroy, template) %> - <%= button_to button_title(title: t('restore'), disabled_with: t('restoring')[..-4], icon: svg_icon('rotate', class: 'w-6 h-6')), template_restore_index_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1' %> - <% end %>
<%= link_to template_preview_path(template), class: 'btn btn-outline btn-sm w-full' do %> From 1a65e7bcffb33f097621d1a560e186da57f606ff Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 29 Aug 2026 21:43:44 +0300 Subject: [PATCH 11/20] adjust download route --- app/controllers/submit_form_completed_download_controller.rb | 2 +- app/javascript/submission_form/completed.vue | 2 +- config/routes.rb | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/controllers/submit_form_completed_download_controller.rb b/app/controllers/submit_form_completed_download_controller.rb index 17503778..7e244fe6 100644 --- a/app/controllers/submit_form_completed_download_controller.rb +++ b/app/controllers/submit_form_completed_download_controller.rb @@ -48,7 +48,7 @@ class SubmitFormCompletedDownloadController < ApplicationController private def submitter_slug - params[:submit_form_slug] || params[:submitter_slug] || params[:submitter_id] + params[:submit_form_slug] || params[:submitter_slug] end def respond_with_combined(submitter) diff --git a/app/javascript/submission_form/completed.vue b/app/javascript/submission_form/completed.vue index 6c07a09c..0cfe3733 100644 --- a/app/javascript/submission_form/completed.vue +++ b/app/javascript/submission_form/completed.vue @@ -230,7 +230,7 @@ export default { download () { this.isDownloading = true - fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`, { + fetch(this.baseUrl + `/s/${this.submitterSlug}/documents`, { method: 'GET', ...this.fetchOptions }).then(async (response) => { diff --git a/config/routes.rb b/config/routes.rb index a1f566d9..a4c09d05 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -175,10 +175,13 @@ Rails.application.routes.draw do resources :submitters, only: %i[] do resources :download, only: %i[index], controller: 'submitters_download', constraints: { submitter_id: /\d+/ } - resources :download, only: %i[index], controller: 'submit_form_completed_download' resources :send_email, only: %i[create], controller: 'submitters_send_email' end + resources :submitters, only: %i[], param: 'slug' do + resources :download, only: %i[index], controller: 'submit_form_completed_download' + end + resources :settings, only: %i[index] scope '/settings', as: :settings do From 66bf6af7baa9ba08b38cd2f2b2ad481ab22d37dc Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 30 Aug 2026 10:56:34 +0300 Subject: [PATCH 12/20] adjust verify pdf signature --- lib/verify_pdf_signature.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/verify_pdf_signature.rb b/lib/verify_pdf_signature.rb index 3543c1c2..ec588e5e 100644 --- a/lib/verify_pdf_signature.rb +++ b/lib/verify_pdf_signature.rb @@ -16,7 +16,8 @@ module VerifyPdfSignature next [] if signatures.blank? verified_signatures = signatures.select { |e| verified_signature?(e, io, trusted_certs) } - last_signature = verified_signatures.max_by(&:signed_end) + trusted_signatures = verified_signatures.select { |e| trusted_signature?(e, trusted_certs) } + last_signature = (trusted_signatures.presence || verified_signatures).max_by(&:signed_end) has_unsigned_changes = last_signature && unsigned_changes?(document, io, last_signature.signed_end) signatures.map do |signature| @@ -72,15 +73,25 @@ module VerifyPdfSignature end def certificate_message(pkcs7, trusted_certs) - public_key = signer_certificate(pkcs7)&.public_key&.to_der - - if trusted_certs.any? { |e| e.public_key.to_der == public_key } + if trusted_certificate?(pkcs7, trusted_certs) MessageStruct.new(text: I18n.t('signed_with_trusted_certificate'), status: :success) else MessageStruct.new(text: I18n.t('signed_with_external_certificate'), status: :error) end end + def trusted_signature?(signature, trusted_certs) + trusted_certificate?(OpenSSL::PKCS7.new(signature.contents), trusted_certs) + rescue OpenSSL::PKCS7::PKCS7Error + false + end + + def trusted_certificate?(pkcs7, trusted_certs) + public_key = signer_certificate(pkcs7)&.public_key&.to_der + + trusted_certs.any? { |e| e.public_key.to_der == public_key } + end + def verify_contents(pkcs7, signed_data, trusted_certs) return false if digest_algorithms(pkcs7).blank? From a1c30c20937a9211bf2792c915bd6f660d42da35 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 30 Aug 2026 11:04:45 +0300 Subject: [PATCH 13/20] update tools verify --- app/controllers/api/tools_controller.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/tools_controller.rb b/app/controllers/api/tools_controller.rb index 09ea9235..aa937c44 100644 --- a/app/controllers/api/tools_controller.rb +++ b/app/controllers/api/tools_controller.rb @@ -17,24 +17,23 @@ module Api def verify file = Base64.decode64(params[:file]) - pdf = HexaPDF::Document.new(io: StringIO.new(file)) trusted_certs = Accounts.load_trusted_certs(current_account) is_checksum_found = CompletedDocument.exists?(sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(file))) render json: { checksum_status: is_checksum_found ? 'verified' : 'not_found', - signatures: pdf.signatures.map do |sig| + signatures: VerifyPdfSignature.call(StringIO.new(file), trusted_certs).map do |sig| { - verification_result: sig.verify(trusted_certs:).messages, - signer_name: sig.signer_name, - signing_reason: sig.signing_reason, + verification_result: sig.messages.map { |m| { type: m.status || :info, content: m.text } }, + signer_name: sig.common_name, + signing_reason: sig.reason, signing_time: sig.signing_time, - signature_type: sig.signature_type + signature_type: sig.type } end } - rescue HexaPDF::MalformedPDFError + rescue Pdfium::PdfiumError render json: { error: 'Malformed PDF' }, status: :unprocessable_content end end From 180b201dcf074d046b84f5dcabdd8ecf9cae036e Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 30 Aug 2026 11:20:50 +0300 Subject: [PATCH 14/20] fix mfa rate limit --- app/controllers/mfa_setup_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/mfa_setup_controller.rb b/app/controllers/mfa_setup_controller.rb index 1860c1ed..702bd6e9 100644 --- a/app/controllers/mfa_setup_controller.rb +++ b/app/controllers/mfa_setup_controller.rb @@ -14,14 +14,14 @@ class MfaSetupController < ApplicationController def edit; end def create + RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true) + if current_user.validate_and_consume_otp!(params[:otp_attempt]) current_user.otp_required_for_login = true current_user.save! redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured') else - RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true) - @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name) @error_message = I18n.t('code_is_invalid') @@ -31,13 +31,13 @@ class MfaSetupController < ApplicationController end def destroy + RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true) + if current_user.validate_and_consume_otp!(params[:otp_attempt]) current_user.update!(otp_required_for_login: false, otp_secret: nil) redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_removed') else - RateLimit.call("mfa-setup-otp-#{current_user.id}", limit: 5, ttl: 5.minutes, enabled: true) - @error_message = I18n.t('code_is_invalid') render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_content From 8756931638fb231ee7e7f56c4eebaf875926f1f9 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 30 Aug 2026 11:22:38 +0300 Subject: [PATCH 15/20] render locked page --- app/controllers/submit_form_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/submit_form_controller.rb b/app/controllers/submit_form_controller.rb index 43db9514..7cde8f69 100644 --- a/app/controllers/submit_form_controller.rb +++ b/app/controllers/submit_form_controller.rb @@ -107,6 +107,8 @@ class SubmitFormController < ApplicationController submitter_version = SubmitterVersion.find_by!(slug: params[:slug] || params[:submit_form_slug]) @submitter = submitter_version.submitter + + maybe_render_locked_page end private From 11dea042ad40b2f8c2b1efaa781425ba0f1979a2 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 30 Aug 2026 11:56:38 +0300 Subject: [PATCH 16/20] adjust test mode auth --- app/controllers/testing_accounts_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/testing_accounts_controller.rb b/app/controllers/testing_accounts_controller.rb index 44274eef..a96f21e5 100644 --- a/app/controllers/testing_accounts_controller.rb +++ b/app/controllers/testing_accounts_controller.rb @@ -6,6 +6,7 @@ class TestingAccountsController < ApplicationController def create authorize!(:manage, current_account) authorize!(:manage, current_user) + authorize!(:manage, EncryptedConfig) impersonate_user(Accounts.find_or_create_testing_user(true_user.account)) From 47361da26f2f13ac19be08873b8de7174b18d386 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 30 Aug 2026 15:36:49 +0300 Subject: [PATCH 17/20] adjust validate required --- lib/submitters/submit_values.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/submitters/submit_values.rb b/lib/submitters/submit_values.rb index 6c83a1ef..3686b71c 100644 --- a/lib/submitters/submit_values.rb +++ b/lib/submitters/submit_values.rb @@ -8,6 +8,7 @@ module Submitters VARIABLE_REGEXP = /\{\{?(\w+)\}\}?/ PHONE_REGEXP = /[+\d()\s-]+/ NONEDITABLE_FIELD_TYPES = %w[stamp heading strikethrough].freeze + REQUIRED_FIELD_TYPES = %w[payment kba verification].freeze STRFTIME_MAP = { 'hour' => '%-k', @@ -95,7 +96,9 @@ module Submitters required_field_uuids_acc.each do |uuid| next if submitter.values[uuid].present? - raise RequiredFieldError, uuid if validate_required + if validate_required || submitter.submission.fields_uuid_index.dig(uuid, 'type').in?(REQUIRED_FIELD_TYPES) + raise RequiredFieldError, uuid + end Rollbar.warning("Required field #{submitter.id}: #{uuid}") if defined?(Rollbar) end From 1fc1444b8fc6d5b25168ffdd1d2761aab0f90578 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 30 Aug 2026 15:49:54 +0300 Subject: [PATCH 18/20] optimize load bmp --- lib/load_bmp.rb | 108 +++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 79 deletions(-) diff --git a/lib/load_bmp.rb b/lib/load_bmp.rb index 387a4a07..53e41200 100644 --- a/lib/load_bmp.rb +++ b/lib/load_bmp.rb @@ -21,40 +21,28 @@ module LoadBmp header_data[:height] ) - if header_data[:bpp] <= 8 - final_pixel_data = decode_indexed_pixel_data( - raw_pixel_data_from_file, - header_data[:bpp], - header_data[:width], - header_data[:height], - header_data[:bmp_stride], - header_data[:color_table] - ) - bands = 3 - else - final_pixel_data = prepare_unpadded_pixel_data_string( - raw_pixel_data_from_file, - header_data[:bpp], - header_data[:width], - header_data[:height], - header_data[:bmp_stride] - ) - bands = header_data[:bpp] / 8 - end - - image = Vips::Image.new_from_memory_copy(final_pixel_data, header_data[:width], header_data[:height], bands, :uchar) - - image = image.flip(:vertical) if header_data[:orientation] == -1 + padded_rows = Vips::Image.new_from_memory_copy( + raw_pixel_data_from_file, + header_data[:bmp_stride], + header_data[:height], + 1, + :uchar + ) image_rgb = if header_data[:bpp] <= 8 - image - elsif bands == 3 - image.recomb(band3_recomb) - elsif bands == 4 - image.recomb(band4_recomb) + decode_indexed_pixel_data(padded_rows, header_data[:bpp], header_data[:width], header_data[:color_table]) + else + bands = header_data[:bpp] / 8 + + image = padded_rows.extract_area(0, 0, header_data[:width] * bands, header_data[:height]) + .bandfold(factor: bands) + + bands == 3 ? image.recomb(band3_recomb) : image.recomb(band4_recomb) end + image_rgb = image_rgb.flip(:vertical) if header_data[:orientation] == -1 + image_rgb = image_rgb.copy(interpretation: :srgb) if image_rgb.interpretation != :srgb image_rgb @@ -170,62 +158,24 @@ module LoadBmp raw_pixel_data_from_file end - def prepare_unpadded_pixel_data_string(raw_pixel_data_from_file, bpp, width, height, bmp_stride) - bytes_per_pixel = bpp / 8 - actual_row_width_bytes = width * bytes_per_pixel - - unpadded_rows = Array.new(height) - current_offset_in_blob = 0 - - height.times do |i| - if current_offset_in_blob + actual_row_width_bytes > raw_pixel_data_from_file.bytesize - raise ArgumentError, - "Not enough data in pixel blob for row #{i}. Offset #{current_offset_in_blob}, " \ - "row width #{actual_row_width_bytes}, blob size #{raw_pixel_data_from_file.bytesize}" - end - - unpadded_row_slice = raw_pixel_data_from_file.byteslice(current_offset_in_blob, actual_row_width_bytes) + def decode_indexed_pixel_data(padded_rows, bpp, width, color_table) + pixels_per_byte = 8 / bpp - if unpadded_row_slice.nil? || unpadded_row_slice.bytesize < actual_row_width_bytes - raise ArgumentError, "Failed to slice a full unpadded row from pixel data blob for row #{i}." - end - - unpadded_rows[i] = unpadded_row_slice - current_offset_in_blob += bmp_stride - end + image = padded_rows.maplut(build_palette_lut(bpp, color_table)) + image = image.bandunfold.bandfold(factor: 3) if pixels_per_byte > 1 - unpadded_rows.join + image.extract_area(0, 0, width, padded_rows.height) end - def decode_indexed_pixel_data(raw_data, bpp, width, height, bmp_stride, color_table) - palette = color_table.map { |r, g, b| [r, g, b].pack('CCC') } - - output = String.new(capacity: width * height * 3) - - height.times do |y| - row_offset = y * bmp_stride - - case bpp - when 1 - width.times do |x| - byte_val = raw_data.getbyte(row_offset + (x >> 3)) - index = (byte_val >> (7 - (x & 7))) & 0x01 - output << palette[index] - end - when 4 - width.times do |x| - byte_val = raw_data.getbyte(row_offset + (x >> 1)) - index = x.even? ? (byte_val >> 4) & 0x0F : byte_val & 0x0F - output << palette[index] - end - when 8 - width.times do |x| - output << palette[raw_data.getbyte(row_offset + x)] - end - end + def build_palette_lut(bpp, color_table) + pixels_per_byte = 8 / bpp + mask = (1 << bpp) - 1 + + entries = Array.new(256) do |byte_val| + (0...pixels_per_byte).flat_map { |i| color_table[(byte_val >> ((pixels_per_byte - 1 - i) * bpp)) & mask] } end - output + Vips::Image.new_from_memory_copy(entries.flatten.pack('C*'), 256, 1, pixels_per_byte * 3, :uchar) end def band3_recomb From 474603e1c95e0b3b4762128002381d0ec2befa01 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 30 Aug 2026 18:51:35 +0300 Subject: [PATCH 19/20] hide list form buttons --- app/javascript/template_builder/import_list.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/javascript/template_builder/import_list.vue b/app/javascript/template_builder/import_list.vue index d5f00711..94291e75 100644 --- a/app/javascript/template_builder/import_list.vue +++ b/app/javascript/template_builder/import_list.vue @@ -366,6 +366,9 @@ export default { } } }, + beforeUnmount () { + document.getElementById('list_form_buttons')?.classList?.add('hidden') + }, methods: { t (key) { return this.i18n[key] || key From d2373c15783874c55e9b1ca8012b960c2c34ac61 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Mon, 31 Aug 2026 08:48:49 +0300 Subject: [PATCH 20/20] fix normalize email --- lib/submissions.rb | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/submissions.rb b/lib/submissions.rb index 9587c74a..59fef7a4 100644 --- a/lib/submissions.rb +++ b/lib/submissions.rb @@ -3,6 +3,11 @@ module Submissions DEFAULT_SUBMITTERS_ORDER = 'random' + SKIP_EMAIL_FIX_LABELS = %w[ga gami gasi gil gma gmao gmi gmsi goi ol].freeze + + SKIP_EMAIL_FIX_TLDS = + /\.(?:gob(?:\.\w+)?|om|mm|cm|et|mo|nz|za|ie|mom|free|comsec|unicom|ed(?:\.\w+){1,2})\z/i + module_function def maybe_update_completed_at(submission) @@ -210,7 +215,7 @@ module Submissions return email.downcase.sub(/@gmail?\z/i, '@gmail.com') if email.match?(/@gmail?\z/i) return email.downcase if email.include?(',') || - email.match?(/\.(?:gob(?:\.\w+)?|om|mm|cm|et|mo|nz|za|ie|ed\.jp)\z/i) || + email.match?(SKIP_EMAIL_FIX_TLDS) || email.exclude?('.') fixed_email = EmailTypo.call(email.delete_prefix('<')) @@ -220,8 +225,7 @@ module Submissions domain = email.split('@').last.to_s.downcase fixed_domain = fixed_email.to_s.split('@').last - return email.downcase if domain == fixed_domain - return email.downcase if fixed_domain.match?(/\Agmail\.(?!com\z)/i) + return email.downcase if domain == fixed_domain || skip_email_fix?(domain, fixed_domain) threshold = fixed_domain.start_with?('hotmail.') ? 2 : 3 @@ -236,6 +240,13 @@ module Submissions fixed_email end + def skip_email_fix?(domain, fixed_domain) + return true if SKIP_EMAIL_FIX_LABELS.include?(domain.split('.').first) + return true if fixed_domain.match?(/\Agmail\.(?!com\z)/i) + + fixed_domain.match?(/\A(?:comcast|verizon)\./i) && !domain.match?(/\A(?:comcast|verizon)\./i) + end + def filtered_conditions_schema(submission, values: nil, include_submitter_uuid: nil) (submission.template_schema || submission.template.schema).filter_map do |item| if item['conditions'].present?