Тема сегодняшней статьи будет касаться XML поскольку сегодня (10 февраля) XML-ю исполнилось 16 лет!
XML и Java дружат очень давно и в интернете можно найти множество учебных материалов посвященных парсингу XML-файлов, но не смотря на это, время от времени встречаю в коде разных программистов некоторые не совсем удачные решения при разборе XML.
Эта статья возможно не будет интересна тем, кто занимается подобными задачами из года в год и парсит XML-ки с закрытами глазами, но возможно для тех кто только начал вникать в задачу, эти пару прописных истин будут полезны.

Итак.

1. Поиск нужных элементов.

Метод Element.getElementsByTagName возвращает список всех подэлементов с указаным именем.

Всех значит прямо всех, не только непосредственных "детей" этого элементов, но и "внуков" (детей их детей) и "правнуков" и т.д.
Другими словами, если у нас есть следующий XML:

<root>
    <element>
         <string>one</string>
         <foo>
              <moo>
                   <string>two</string>
              </moo>
         </foo>
    </element>
</root>

Тогда при его обработке:

   Element element = ... ; // каким-то образом получили element
   NodeList nodeList = element.getElementsByTagName("string");
   int length = nodeList.getLength();
   for (int i=0;i<length;++i) {
        Element el = (Element) nodeList.item(i);
        System.out.println(el.getTagName() +":" + el.getTextContent());
   }

Вернутся элемент "one" и элемент "two".
По моим наблюдениям, наиболее часто такую не очень оптимальную обработку XML-файлов используют Java программисты при разборе iOS-ных plist-ов, в итоге разбор скорость разбора файла сильно замедляется.

2. Безопасный парсинг.

Предположим вы принимаете в качестве запроса XML-файлики вида:

  <?xml version="1.0" encoding="UTF-8"?>
  <doc>
     <id>1</id>
     <date>10.02.2014</date>
     <message>hello world</message>
  </doc>

Тогда если злоумышленник отправит вам вместо этого:

    <!DOCTYPE doc [ <!ENTITY xxe SYSTEM "file:///C:/" > ]>
    <doc>
     <id>1</id>
     <date>10.02.2014</date>
     <message>&xxe;</message>
   </doc>

Тогда содержимое элемента "message" будет список файлов с вашего диска С (ничто не мешает указать не директорию, а запросить содержимое файла, например: SYSTEM "file:///etc/password").
Это возможно например в случае если код обработки XML-ки выглядит приблизительно следующим образом:

        // упрощенный пример 
        File f = new File("xxe.xml");
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(f);
        Element root = document.getDocumentElement();
        // для простоты сразу берем message
        Element message = (Element) root.getElementsByTagName("message").item(0);
        String textContent = message.getTextContent(); // тоже для упрощения
        System.out.println(textContent);

Это не очень приятно, т.к. гипотетически возможны следующие сценарии дальнейшего развития события:
1. Ваш серверный код умный, и тогда вернет ошибку некорректное значение такое-то:"содержимое файла" и злоумышленник получит результат сразу.
2. Ваш код не делает никаких проверок, сохраняет запись. Тогда злоумышленник получит данные потом, запросив содержимое документа.

Самое главное, что это не какая-то ошибка, а вполне законная работа API, которая так и должна работать и до сих пор работает из коробки в Java 7.
Такой способ взлома уже используется наверное больше десяти лет, но программисты продолжают его игнорировать.

Для того, чтобы обезопасится, нужно предпринять некоторые усилия - указать дополнительные настройки XML-парсера, ограничить права доступа и т.д.
Какие конкретно настройки указывать можно найти с помощью поисковика по запросу "XML External Entity" или посмотреть на сайте OWASP.

Надеюсь эти пару советов помогут многим Java программистам, которые начинают заниматься разбором XML не делать лишних ошибок.