مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2026-05-31 04:35:42 +00:00
refactor(tracking): remove unused src image proxy
The /img/<server>/<message> endpoint accepted a src=<url> query parameter and proxied the body of that URL back to the caller. Nothing in the codebase ever produces a src= parameter — the parser only inserts a plain tracking pixel and rewrites href links — so this branch is dead code inherited from the original AppMail import. Drop the src branch: requests with src now return 400. The no-src path that serves the tracking pixel and records loads is unchanged, and a spec covers both the pixel-serving path and the removed branch.
هذا الالتزام موجود في:
@@ -48,25 +48,11 @@ class TrackingMiddleware
|
|||||||
Sentry.capture_exception(e) if defined?(Sentry)
|
Sentry.capture_exception(e) if defined?(Sentry)
|
||||||
end
|
end
|
||||||
|
|
||||||
source_image = request.params["src"]
|
if request.params["src"].nil?
|
||||||
case source_image
|
|
||||||
when nil
|
|
||||||
headers = {}
|
headers = {}
|
||||||
headers["Content-Type"] = "image/png"
|
headers["Content-Type"] = "image/png"
|
||||||
headers["Content-Length"] = TRACKING_PIXEL.bytesize.to_s
|
headers["Content-Length"] = TRACKING_PIXEL.bytesize.to_s
|
||||||
[200, headers, [TRACKING_PIXEL]]
|
[200, headers, [TRACKING_PIXEL]]
|
||||||
when /\Ahttps?:\/\//
|
|
||||||
response = Postal::HTTP.get(source_image, timeout: 3)
|
|
||||||
return [404, {}, ["Not found"]] unless response[:code] == 200
|
|
||||||
|
|
||||||
headers = {}
|
|
||||||
headers["Content-Type"] = response[:headers]["content-type"]&.first
|
|
||||||
headers["Last-Modified"] = response[:headers]["last-modified"]&.first
|
|
||||||
headers["Cache-Control"] = response[:headers]["cache-control"]&.first
|
|
||||||
headers["Etag"] = response[:headers]["etag"]&.first
|
|
||||||
headers["Content-Length"] = response[:body].bytesize.to_s
|
|
||||||
[200, headers, [response[:body]]]
|
|
||||||
|
|
||||||
else
|
else
|
||||||
[400, {}, ["Invalid/missing source image"]]
|
[400, {}, ["Invalid/missing source image"]]
|
||||||
end
|
end
|
||||||
|
|||||||
71
spec/lib/tracking_middleware_spec.rb
Normal file
71
spec/lib/tracking_middleware_spec.rb
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
require "rack/test"
|
||||||
|
|
||||||
|
RSpec.describe TrackingMiddleware do
|
||||||
|
include Rack::Test::Methods
|
||||||
|
|
||||||
|
let(:inner_app) { ->(_env) { [200, {}, ["inner"]] } }
|
||||||
|
let(:app) { described_class.new(inner_app) }
|
||||||
|
|
||||||
|
let(:server) { create(:server) }
|
||||||
|
let(:message) do
|
||||||
|
MessageFactory.incoming(server) do |_msg, mail|
|
||||||
|
mail.html_part = Mail::Part.new do
|
||||||
|
content_type "text/html; charset=UTF-8"
|
||||||
|
body "<html><body>hi</body></html>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def track_headers
|
||||||
|
{ "HTTP_X_POSTAL_TRACK_HOST" => "1" }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /img/:server_token/:message_token (open tracking pixel)" do
|
||||||
|
before do
|
||||||
|
get "/img/#{server.token}/#{message.token}", {}, track_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the tracking pixel PNG" do
|
||||||
|
expect(last_response.status).to eq 200
|
||||||
|
expect(last_response.headers["Content-Type"]).to eq "image/png"
|
||||||
|
expect(last_response.body.bytesize).to be > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
it "records a load for the message" do
|
||||||
|
# Re-fetch the message so loads are read fresh from the DB.
|
||||||
|
reloaded = server.message_db.message(message.id)
|
||||||
|
expect(reloaded.loads.size).to eq 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "GET /img/:server_token/:message_token?src=<url> (image proxy)" do
|
||||||
|
let(:attacker_url) { "http://internal.example.com/secret" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, attacker_url).to_return(status: 200, body: "internal-secret")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not fetch the URL and returns 400" do
|
||||||
|
get "/img/#{server.token}/#{message.token}", { src: attacker_url }, track_headers
|
||||||
|
|
||||||
|
expect(last_response.status).to eq 400
|
||||||
|
expect(WebMock).not_to have_requested(:get, attacker_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not fetch the URL even when the message token is invalid" do
|
||||||
|
get "/img/#{server.token}/nonexistent", { src: attacker_url }, track_headers
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:get, attacker_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when the track-host header is missing" do
|
||||||
|
it "passes the request through to the inner app untouched" do
|
||||||
|
get "/img/#{server.token}/#{message.token}"
|
||||||
|
expect(last_response.body).to eq "inner"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم