instructure/canvas-lms · error · CCImportError

Invalid xml syntax

Error message

Invalid xml syntax

What it means

After namespace validation, convert_blti_xml runs XPath queries via convert_blti_link. A Nokogiri::XML::XPath::SyntaxError rescued there is re-raised as CCImportError 'Invalid xml syntax', meaning the document could not be evaluated with the expected XPath expressions — typically malformed or structurally unexpected XML.

Solutions

  1. Validate the XML with a parser (xmllint --noout config.xml) and fix syntax errors such as unescaped & or unclosed tags
  2. Log/inspect response.body before conversion to see exactly what the server returned
  3. Re-download the configuration XML from the tool provider

Example fix

# before (invalid)
<launch_url>https://tool.example.com/launch?a=1&b=2</launch_url>

# after (valid)
<launch_url>https://tool.example.com/launch?a=1&amp;b=2</launch_url>
Defensive patterns

Strategy: validation

Validate before calling

Nokogiri::XML(xml) { |c| c.strict }.parse # raises SyntaxError before import if malformed

Type guard

def well_formed_xml?(xml)
  Nokogiri::XML(xml) { |c| c.strict }
  true
rescue Nokogiri::XML::SyntaxError
  false
end

Try / catch

begin
  tool = converter.convert_blti_xml(xml)
rescue CCImportError => e
  if e.message.include?('Invalid xml syntax')
    Rails.logger.error("Malformed BLTI XML: #{xml.first(200)}")
  end
  raise
end

Prevention

When it happens

Trigger: convert_blti_xml is given XML that parses loosely but breaks the XPath evaluation inside convert_blti_link — e.g. truncated documents, unescaped characters, or content served with a non-XML doctype/structure.

Common situations: Tool config URL returns truncated HTML/XML; the XML contains unescaped ampersands or invalid entities; a proxy mangles the response body; the vendor serves a partial file.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/624feb96bb70f324. Report an issue: GitHub.

Appendix: source

Thrown at lib/cc/importer/blti_converter.rb:120

      end
      if (icon = get_node_val(doc, "#{link_css_path} > #{blti}|icon"))
        tool[:settings] ||= {}
        tool[:settings][:icon_url] = icon.strip
      end
      tool
    end

    def convert_blti_xml(xml)
      doc = create_xml_doc(xml)
      unless doc.namespaces.to_s.downcase.include? "imsglobal"
        raise CCImportError, I18n.t("Invalid XML Configuration")
      end

      begin
        tool = convert_blti_link(doc)
        check_for_unescaped_url_properties(tool) if tool
      rescue Nokogiri::XML::XPath::SyntaxError
        raise CCImportError, I18n.t(:invalid_xml_syntax, "Invalid xml syntax")
      end
      tool
    end

    def check_for_unescaped_url_properties(obj)
      # Recursively look for properties named 'url'
      case obj
      when Hash
        obj.select { |k, v| k.to_s == "url" && v.is_a?(String) }
           .each_value { |v| check_for_unescaped_url(v) }
        obj.each_value { |v| check_for_unescaped_url_properties(v) }
      when Array
        obj.each { |o| check_for_unescaped_url_properties(o) }
      end
    end

    def check_for_unescaped_url(url)
      if /(.*[^=]*\?*=)[^&;]*=/.match?(url)

View on GitHub (pinned to 1c9f0bb801)