Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -560,31 +560,54 @@ private static List<String> getFeaturesFrom(IQ packet) {
/**
* Extracts a list of extended service discovery information from an IQ
* packet.
*
* Per XEP-0115 §5.4 item 3f, forms where the FORM_TYPE field is not of type "hidden" or where the form does not include a
* FORM_TYPE field are ignored.
*
* @param packet
* the packet
* @return a list of extended service discoverin information features.
* @return a list of extended service discovery information features.
*/
private static List<String> getExtendedDataForms(IQ packet) {
List<String> results = new ArrayList<>();
Element query = packet.getChildElement();
if (query == null) {
return results;
}
Iterator<Element> extensionIterator = query.elementIterator(QName.get(
"x", "jabber:x:data"));
if (extensionIterator != null) {
while (extensionIterator.hasNext()) {
Element extensionElement = extensionIterator.next();

// Find the FORM_TYPE field first to determine if this form should be included.
// Per XEP-0115 §5.4 item 3f: if no FORM_TYPE field exists, or if FORM_TYPE is not type='hidden',
// ignore the form but continue processing other forms.
Element formTypeField = null;
Iterator<Element> fieldCheckIterator = extensionElement.elementIterator("field");
while (fieldCheckIterator != null && fieldCheckIterator.hasNext()) {
Element fieldEl = fieldCheckIterator.next();
if ("FORM_TYPE".equals(fieldEl.attributeValue("var"))) {
formTypeField = fieldEl;
break;
}
}

if (formTypeField == null || !"hidden".equals(formTypeField.attributeValue("type"))) {
// Ignore this form as per XEP-0115 §5.4 item 3f.
continue;
}

final StringBuilder formType = new StringBuilder();
formType.append(formTypeField.element("value").getText());
formType.append('<');

Iterator<Element> fieldIterator = extensionElement
.elementIterator("field");
List<String> vars = new ArrayList<>();
while (fieldIterator != null && fieldIterator.hasNext()) {
final Element fieldElement = fieldIterator.next();
if (fieldElement.attributeValue("var").equals("FORM_TYPE")) {
formType
.append(fieldElement.element("value").getText());
formType.append('<');
} else {
if (!"FORM_TYPE".equals(fieldElement.attributeValue("var"))) {
final StringBuilder var = new StringBuilder();
var.append(fieldElement.attributeValue("var"));
var.append('<');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,109 @@ public void test() throws Exception
// Verify results.
assertEquals("89D/mEGBT1K0RtY28gEkGRbV2rc=", result);
}

/**
* Tests that a data form without a FORM_TYPE field is excluded from the verification string
* per XEP-0115 §5.4 item 3f.
*
* @see <a href="https://igniterealtime.atlassian.net/browse/OF-3217">OF-3217</a>
*/
@Test
public void testFormWithoutFormTypeIsIgnored() throws Exception {
// Setup: a simple IQ with one identity, one feature, and a data form without FORM_TYPE.
final IQ iq = new IQ(IQ.Type.result);
iq.setFrom("test@example.com/res");
iq.setTo("server@example.com");
iq.setID("test1");

final Element query = iq.setChildElement("query", "http://jabber.org/protocol/disco#info");

final Element identity = query.addElement("identity");
identity.addAttribute("category", "client");
identity.addAttribute("type", "pc");
identity.addAttribute("name", "TestClient");

query.addElement("feature").addAttribute("var", "http://jabber.org/protocol/caps");

// Add a data form WITHOUT a FORM_TYPE field – should be ignored.
final Element formWithoutFormType = query.addElement(QName.get("x", "jabber:x:data"));
formWithoutFormType.addAttribute("type", "result");
final Element customField = formWithoutFormType.addElement("field");
customField.addAttribute("var", "custom");
customField.addElement("value").setText("custom-value");

// Compute the ver hash including the form, as the old (incorrect) code would.
// This should produce the same hash as a response without any data form,
// because the form without FORM_TYPE must be ignored.
final IQ iqWithoutForm = new IQ(IQ.Type.result);
iqWithoutForm.setFrom("test@example.com/res");
iqWithoutForm.setTo("server@example.com");
iqWithoutForm.setID("test2");
final Element query2 = iqWithoutForm.setChildElement("query", "http://jabber.org/protocol/disco#info");
final Element identity2 = query2.addElement("identity");
identity2.addAttribute("category", "client");
identity2.addAttribute("type", "pc");
identity2.addAttribute("name", "TestClient");
query2.addElement("feature").addAttribute("var", "http://jabber.org/protocol/caps");

// Execute.
final String hashWithIgnoredForm = EntityCapabilitiesManager.generateVerHash(iq, "sha-1");
final String hashWithoutForm = EntityCapabilitiesManager.generateVerHash(iqWithoutForm, "sha-1");

// Verify: the form without FORM_TYPE should be ignored, so hashes must be equal.
assertEquals(hashWithoutForm, hashWithIgnoredForm,
"A data form without a FORM_TYPE field must be ignored when computing the ver hash.");
}

/**
* Tests that a data form with a FORM_TYPE field that is not of type 'hidden' is excluded
* from the verification string per XEP-0115 §5.4 item 3f.
*
* @see <a href="https://igniterealtime.atlassian.net/browse/OF-3217">OF-3217</a>
*/
@Test
public void testFormWithNonHiddenFormTypeIsIgnored() throws Exception {
// Setup: a simple IQ with one identity, one feature, and a data form with non-hidden FORM_TYPE.
final IQ iq = new IQ(IQ.Type.result);
iq.setFrom("test@example.com/res");
iq.setTo("server@example.com");
iq.setID("test3");

final Element query = iq.setChildElement("query", "http://jabber.org/protocol/disco#info");

final Element identity = query.addElement("identity");
identity.addAttribute("category", "client");
identity.addAttribute("type", "pc");
identity.addAttribute("name", "TestClient");

query.addElement("feature").addAttribute("var", "http://jabber.org/protocol/caps");

// A form with FORM_TYPE of type 'text-single' (not 'hidden') – should be ignored.
final Element formWithNonHiddenFormType = query.addElement(QName.get("x", "jabber:x:data"));
formWithNonHiddenFormType.addAttribute("type", "result");
final Element formTypeField = formWithNonHiddenFormType.addElement("field");
formTypeField.addAttribute("var", "FORM_TYPE");
formTypeField.addAttribute("type", "text-single"); // NOT hidden
formTypeField.addElement("value").setText("urn:example:form");

// Compute reference without any form.
final IQ iqWithoutForm = new IQ(IQ.Type.result);
iqWithoutForm.setFrom("test@example.com/res");
iqWithoutForm.setTo("server@example.com");
iqWithoutForm.setID("test4");
final Element query2 = iqWithoutForm.setChildElement("query", "http://jabber.org/protocol/disco#info");
final Element identity2 = query2.addElement("identity");
identity2.addAttribute("category", "client");
identity2.addAttribute("type", "pc");
identity2.addAttribute("name", "TestClient");
query2.addElement("feature").addAttribute("var", "http://jabber.org/protocol/caps");

// Execute.
final String hashWithIgnoredForm = EntityCapabilitiesManager.generateVerHash(iq, "sha-1");
final String hashWithoutForm = EntityCapabilitiesManager.generateVerHash(iqWithoutForm, "sha-1");

// Verify: the form with non-hidden FORM_TYPE should be ignored.
assertEquals(hashWithoutForm, hashWithIgnoredForm,
"A data form whose FORM_TYPE field is not of type 'hidden' must be ignored when computing the ver hash.");
}
}
Loading