diff --git a/lib/api.rb b/lib/api.rb index 2245cec63df..7e9891e027b 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -484,6 +484,7 @@ module Api # otherwise let HostUrl figure out what host is appropriate if self.respond_to?(:request) host, protocol = get_host_and_protocol_from_request + target_shard = Shard.current elsif self.respond_to?(:use_placeholder_host?) && use_placeholder_host? host = PLACEHOLDER_HOST protocol = PLACEHOLDER_PROTOCOL @@ -505,7 +506,11 @@ module Api end html = rewriter.translate_content(html) - url_helper = Html::UrlProxy.new(self, context, host, protocol) + url_helper = Html::UrlProxy.new(self, + context, + host, + protocol, + target_shard: target_shard) account = Context.get_account(context) || @domain_root_account include_mobile = !(respond_to?(:in_app?, true) && in_app?) Html::Content.rewrite_outgoing(html, account, url_helper, include_mobile: include_mobile) diff --git a/lib/api/html/url_proxy.rb b/lib/api/html/url_proxy.rb index 1dfd9654396..2481a3edc70 100644 --- a/lib/api/html/url_proxy.rb +++ b/lib/api/html/url_proxy.rb @@ -75,12 +75,13 @@ module Api }.freeze class UrlProxy - attr_reader :proxy, :context, :host, :protocol - def initialize(helper, context, host, protocol) + attr_reader :proxy, :context, :host, :protocol, :target_shard + def initialize(helper, context, host, protocol, target_shard: nil) @proxy = helper @context = context @host = host @protocol = protocol + @target_shard = target_shard || context.shard end def media_object_thumbnail_url(media_id) @@ -119,7 +120,15 @@ module Api # otherwise if it starts with a slash, it's a path that needs to be # made absolute with the canvas hostname prepended if !url.host && url_str[0] == '/'[0] - element[attribute] = "#{protocol}://#{host}#{url_str}" + # transpose IDs in the URL + if context.shard != target_shard && (args = recognize_path(url_str)) + transpose_ids(args) + args[:host] = host + args[:protocol] = protocol + element[attribute] = proxy.url_for(args) + else + element[attribute] = "#{protocol}://#{host}#{url_str}" + end api_endpoint_info(url_str).each do |att, val| element[att] = val end @@ -138,6 +147,8 @@ module Api helper = api_route[1] args = { :protocol => protocol, :host => host } args.merge! Hash[api_route.slice(2, match.captures.size).zip match.captures] + # transpose IDs in the URL + transpose_ids(args) if context.shard != target_shard if args[:url] && (return_type == 'SessionlessLaunchUrl' || (return_type == "Page" && url.include?("titleize=0"))) args[:url] = URI.unescape(args[:url]) end @@ -146,6 +157,44 @@ module Api end {} end + + # based on ActionDispatch::Routing::RouteSet#recognize_path, but returning all params, + # not just path_params. and failures return nil, not raise an exception + def recognize_path(path) + path = ActionDispatch::Journey::Router::Utils.normalize_path(path) + + begin + env = Rack::MockRequest.env_for(path, method: "GET") + rescue URI::InvalidURIError + return nil + end + + req = Rails.application.routes.send(:make_request, env) + Rails.application.routes.router.recognize(req) do |route, params| + params.each do |key, value| + if value.is_a?(String) + value = value.dup.force_encoding(Encoding::BINARY) + params[key] = URI.parser.unescape(value) + end + end + req.path_parameters = params + app = route.app + if app.matches?(req) && app.dispatcher? + return req.params + end + end + + nil + end + + def transpose_ids(args) + args.each_key do |key| + if (key.to_s == 'id' || key.to_s.end_with?('_id')) && + (new_id = Switchman::Shard.relative_id_for(args[key], context.shard, target_shard)) + args[key] = Switchman::Shard.short_id_for(new_id) + end + end + end end end end diff --git a/spec/lib/api/html/content_spec.rb b/spec/lib/api/html/content_spec.rb index e0efd2557b1..8a49088fede 100644 --- a/spec/lib/api/html/content_spec.rb +++ b/spec/lib/api/html/content_spec.rb @@ -97,7 +97,7 @@ module Api it "re-writes root-relative urls to be absolute" do string = "

" - url_helper = UrlProxy.new(double, double, "example.com", "https") + url_helper = UrlProxy.new(double, double(shard: nil), "example.com", "https") html = Content.new(string).rewritten_html(url_helper) expect(html).to eq("

") end diff --git a/spec/lib/api_spec.rb b/spec/lib/api_spec.rb index 50bb21bbaf4..5cdf34c761f 100644 --- a/spec/lib/api_spec.rb +++ b/spec/lib/api_spec.rb @@ -743,6 +743,35 @@ describe Api do expect(res).to eq "

a

b

" end end + + context "sharding" do + specs_require_sharding + + it 'transposes ids in urls' do + @shard1.activate do + a = Account.create! + student_in_course(account: a) + end + html = <<-HTML + +link + HTML + + @k = klass.new + @k.instance_variable_set(:@domain_root_account, Account.default) + @k.extend Rails.application.routes.url_helpers + @k.extend ActionDispatch::Routing::UrlFor + allow(@k).to receive(:request).and_return(nil) + allow(@k).to receive(:get_host_and_protocol_from_request).and_return(['school.instructure.com', 'https']) + allow(@k).to receive(:url_options).and_return({}) + + res = @k.api_user_content(html, @course, @student) + expect(res).to eq <<-HTML + +link + HTML + end + end end context ".process_incoming_html_content" do