Skip to content

Instantly share code, notes, and snippets.

@slayerlab
Last active April 16, 2018 02:44
Show Gist options
  • Save slayerlab/491d0de7edbe94fc1e2b546490199055 to your computer and use it in GitHub Desktop.
Save slayerlab/491d0de7edbe94fc1e2b546490199055 to your computer and use it in GitHub Desktop.
[unofficial] libxml2 Retrieve Elements through by reading file, in C – part 1 of 2 - written by @SLAYEROWNER

###1 - Tipos de dados

xmlChar é um tipo unsigned char para tratar strings com formato UTF-16. Neste caso, o arquivo XML deve estar no encoding UTF-8 (char) ou UTF-16 (unsigned char). Se a declaração do encode não estiver presente, o xmlChar automaticamente irá converter o arquivo para UTF-8!

xmlDoc é uma estrutura que irá armazenar as informações da tree, por exemplo: filename, children, last child, parent, next node, previous node, xml version, encoding, URL, namespace e etc. É uma variedade de informações analisadas e organizadas no momento em que usado a função "parser" - xmlDocPtr seria um ponteiro para essa estrutura:

xmlDocPtr xmlParseFile(const char *filename);
xmlDocPtr xmlReadFile(const char *filename,
                      const char *encoding,
                      int options);

libxml2-datatypes

######imagem retirada deste link: libXML2, xmlsoft.org

ps: set encoding/options to 0 for null. Definido em: Github: libxml2, include/libxml/tree.h#L551

xmlNode é uma estrutura que contém as informações do node atual, trata-se que estamos analisando um XML node-by-node e essa função tem um ponteiro chamado xmlNodePtr para que consigamos "caminhar" identificando os demais nodes contidos no arquivo XML.

Os membros da classe de xmlNodePtr é definido em: Github: libxml2, include/libxml/tree.h#L487

PS: Ao verificar os membros da "struct" conhecerá os recursos disponíveis para manipular os nodes.

A principio, para criar um analisador (parser) de XML é preciso:

[+] Inserir o arquivo XML.

[+] Detectar o primeiro (root) node.

Caso apenas isso não seja suficiente, e quer buscar informações contidas:

[+] Detectar o primeiro children do node principal. E, asssim, ir avançando até chegar no escopo que está o node a ser manipulado.

[+] Extrair informações.

Obtendo como base as informações acima, o código example1.c mostra como criar um simples analisador XML.

###2 - XML PARSING ####MONTANDO UM PARSING SIMPLES

/* filename: example1.c */
#include <stdio.h>
#include <stdlib.h>
#include <libxml/parser.h>

int main(int argc, char *argv[])
{
  xmlDocPtr  doc;         // tipo de dado para tratar o "file.xml"
  xmlNodePtr curnode;     // tipo de dado para tratar os conteúdos (leia sobre xmlNode)

  doc = xmlReadFile(argv[1], 0, 0);     // montando uma "tree" para o file.xml ser analisado
  if (!argv[1])           // imagine que, argv[1] == file.xml
  {
    fprintf(stderr, "[!] The filename \"%s\" is empty.\n", argv[1]);
    exit(EXIT_FAILURE);
  }

  curnode = xmlDocGetRootElement(doc);  // detecta o primeiro node em file.xml
  if (!curnode)           // caso não encontre nenhum node em file.xml 
  {
    puts("[!] Make sure that is a correct file.\n");
    xmlFreeDoc(doc);      // desfaz toda a estrutura criada
    return 1;
  }

/* Espera-se que o primeiro node em file.xml chama-se "root_node" */
  if (xmlStrcmp(curnode->name, (xmlChar *)"root_node"))
  {
    puts("[!] This node isn`t root node.\n");
    xmlFreeDoc(doc);      // desfaz toda a estrutura criada
    return 1;
  }
  
  return 0;
}

O código acima irá apenas analisar o arquivo inserido e criar uma estrutura através da função xmlReadFile. Com o arquivo XML inserido, usa-se a função xmlDocGetRootElemet, e será detectado o primeiro node do arquivo que foi passado em seu parametro. - Se o node não for encontrado, um NULL será retornado. Na comparação é usado o xmlStrcmp para validar se o node principal é o mesmo que o esperado. Então, verifica se curnode->name é igual à "root_node".

####PRINT RESULT OF "PARSING" O código abaixo irá buscar as informações contidas em <tagname>, que o mesmo está dentro do root node (que chamamos de "root_node" no exemplo do código acima [exemple1.c]).

Imagine o seguinte cenário: [file.xml]

<root_node>
	<tagname>thrash 'til death</tagname>
</root_node>

Deste modo, a função xmlNodeListGetString retonará a frase: "thrash 'til death".

É possível utilizar um loop para que encontre todos os children dentro de <tagname> que sejam de mesma identificação, por exemplo:

[file2.xml]

<root_node>
	<tagname>thrash 'til death</tagname>
	<tagname>slayer</tagname>
	<tagname>sodom</tagname>
</root_node>

Com o exemplo de "file2.xml", quando aplicado o loop o resultado esperado seria imprimir os 3 childrens <tagname>.

/* filename: print_parse.c */
void get_children_element(xmlDocPtr doc, xmlNodePtr curnode)
{
  /* set "curnode" to point to the first child */
  xmlChar *value;
  curnode = curnode->xmlChildrenNode;
  do {
    if (!xmlStrcmp(curnode->name, (const xmlChar *)"tagname"))
    {
      value = xmlNodeListGetString(doc, curnode->xmlChildrenNode, 1);
      printf("[tagname]: %s\n", (char *)value);
      xmlFree(value);
    }
  curnode = curnode->next;
  } while(0);

  return 0;
}

Estes códigos de exemplos não irão funcionar se você não mencionou o node principal e o children node numa mesma função! Isso vai depender em qual escopo a children está.

###3 - Lidando com childrens mais profundos

Aproveitando que estamos num assunto mais técnico, peguei dois arquivos XML, das quais, suas estrutuaras são diferentes, repare o começo de cada um:

####Exemplo Wordpress Plugin RSS XML

wp-plugins/wp-pipes

<?xml version="1.0" encoding="iso-8859-1"?>
<extension version="2.5" type="plugin" group="wppipes-adapter" method="upgrade">
	<name>RSS</name>
	<author>wppipes.com</author>
	...
</extension>	

####Exemplo Exploit-DB RSS XML

exploit-db.com/rss.xml

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Exploit-DB Updates</title>
    <link>https://www.exploit-db.com</link>
  ...
  </channel>
</rss>

PS: A primeira linha pode ignorar, pois nada mais é do que o tipo de encode para o XML.

De acordo com os exemplos acima, o node principal está na linha 2, e a outra, na linha 3 -, respectivamente.

####Lidando com Wordpress Plugin RSS XML Em primeiro lugar, identificar o <extension> que é o node principal e pode-se usar do xmlStrcmp para buscar a children <author> ou, de no modo direto, usar a função ``.

A libXML2 define uma macro xmlChildrenNode que representa o children - ou seja, curnode->xmlChildrenNode é o mesmo que curnode->children. Declarando curnode->xmlChildrenNode, estará buscando o primeiro children do node atual, que seria o <name> e usando curnode->next encontraremos o <author>. Neste caso, o código acima pode ser feito em apenas uma função. Veremos:

(Aproveitando o código acima)

/* filename: extension.c */ 
#include <stdio.h>
/* Mesmo que o modulo parse.c já faz
a inclusão do stdlib, prefiro incluir
pra deixar mais legível. A lib não irá
incluir duas vezes porque ele faz o 
teste "HAVE_STDLIB_H". */
#include <stdlib.h>
#include <libxml/parser.h>

void main(int argc, char *argv[])
{
  xmlDocPtr  doc;         // tipo de dado para tratar o "file.xml"
  xmlNodePtr curnode;     // tipo de dado para tratar os conteúdos (leia sobre xmlNode)

  doc = xmlReadFile(argv[1], 0, 0);     // montando uma "tree" para o file.xml ser analisado
  if (!argv[1])           // imagine que, argv[1] == file.xml
  {
    fprintf(stderr, "[!] The filename \"%s\" is empty.\n", argv[1]);
    exit(EXIT_FAILURE);
  }

  curnode = xmlDocGetRootElement(doc);  // detecta o primeiro node em file.xml
  if (!curnode)           // caso não encontre nenhum node em file.xml 
  {
    puts("[!] Make sure that is a correct file.\n");
    xmlFreeDoc(doc);      // desfaz a estrutura criada
    exit(EXIT_FAILURE);
  }


/* Note: pequena modificação na linha abaixo 
	 Espera-se que o primeiro node em file.xml chama-se "extension" */
  if (xmlStrcmp(curnode->name, (xmlChar *)"extension")) 
  {
    puts("[!] This node isn`t root node.\n");
    xmlFreeDoc(doc);      // desfaz a estrutura criada
    exit(EXIT_FAILURE);
  }
  
/* Função incluida para buscar o "author" children */
	curnode = curnode->xmlChildrenNode;
	do {
		if (!xmlStrcmp(curnode->name, (const xmlChar *)"extension"))
		{
			xmlChar *value = xmlNodeListGetString(doc, curnode->xmlChildrenNode, 1);
			fprintf(stdout, "%s\n", (char *)value); // cast to "char" (utf-8) to avoid bad chars 
			xmlFree(value);
		}
		curnode = curnode->next;
	}while(curnode);
	
  exit(EXIT_SUCCESS);
}

Esquece a função principal por enquanto, foca na função que irá caminhar pelos nodes e buscar as strings.

Quanto mais interno estiver o "element child" estiver, o trabalho será ainda maior. Pode ser usado a função xmlDocGetRootElement(xmlDocPtr *) para buscar o root node e passar como parametro a variável que armazena o arquivo XML. Um esquema mais óbvio de char no node desejado é ir buscando sempre o parent node até chegar no mesmo escopo.

O parente node do elemento title é o <channel>. Ao chegar nele, para caminhar em seu escopo basta jogar uma função para que faça uma comparação em cada node até encontrar o </channel> e encerra a busca.

Em prática, para chegar em channel pode ser feito algo deste jeito:

####Montando um parser para o Exploit-DB RSS XML

void xml_parser(char *file)		// parsing com `char *argv[]`
{
	xmlDocPtr doc = xmlReadFile(file, 0, 0);
	if (!doc)
	{
		fprintf(stderr, "[!] The file \"%s\".\n", argv[1]);
		exit(EXIT_FAILURE);
	}
	
	xmlNodePtr curnode = xmlDocGetRootElement(doc);
	if (!curnode)
	{
		fprintf("[!] Make sure that the \"%s\" is an XML\n");
		xmlFreeDoc(doc);
		exit(EXIT_FAILURE);
	}
	if (!xmlStrcmp(curnode->name, (const xmlChar *)"rss"))
	{
		puts("[!] This node isn`t root node.\n");
		xmlFreeDoc(doc);
		exit(EXIT_FAILURE);
	}
	
	curnode = curnode->xmlChildrenNode;
	do {
		if (!xmlStrcmp(curnode->name, (const xmlChar *)"channel"))
		{
			get_title(doc, curnode);	// função que fará a busca do <title>
		}
		curnode = curnode->next;
	}while(curnode);
	
	xmlFreeDoc(doc);
}

E para buscar o <title> defina uma função que compare com os node -, se for um específico pode ser usado o xmlStrncmp, ou, pode ser usado xmlStrcmp. Ambos são similares a função strncmp e strcmp da glibc.

void get_title(xmlDocPtr doc, xmlNodePtr curnode)
{
	curnode = curnode->xmlChildrenNode;
	do {
		if (!xmlStrcmp(curnode->name, (const xmlChar *)"title"))
		{
			xmlDocPtr *value = xmlNodeListGetString(doc, curnode->xmlChildrenNode, 1);
			fprintf(stdout,"%s\n", (char *)value);
			xmlFree(value); /* Lembre-se de usar sempre esta função quando 
			                   estiver usando xmlNodeListGetString, pois 
			                   ela não irá limpar os valores armazenados.
			                   - Não livrando os valores, pode causar Overflow
			                   e se usar xmlFree e xmlFreeDoc, estará livrando 
			                   2x seguidas, causando Memory Corruption... 
			                   Fique atento! */
		}
		curnode = curnode->next;
	}while(curnode);
}

#####Colocando tudo junto...

#include <stdio.h>
#include <stdlib.h>
#include <libxml/parser.h>

void get_title(xmlDocPtr doc, xmlNodePtr curnode);
void xml_parser(char *file);

int main(int argc, char *argv[])
{
  if (argc != 2)
  {
    fprintf(stderr, "[!] Usage: %s <file.xml>\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  xml_parser(argv[1]);

  return 0;
}

void xml_parser(char *file)
{
  xmlDocPtr doc = xmlReadFile(file, 0, 0);
  if (!doc)
  {
    puts("[!] The filename \"%s\" is empty.\n");
    exit(EXIT_FAILURE);
  }

  xmlNodePtr curnode  = xmlDocGetRootElement(doc);
  if (!curnode)
  {
    puts("[!] Make suere that the \"%s\" is an XML file.\n");
    xmlFreeDoc(doc);
    exit(EXIT_FAILURE);
  }
  if (xmlStrcmp(curnode->name, (const xmlChar *)"rss"))
  {
    puts("[!] This node isn`t root node.\n");
    xmlFreeDoc(doc);
    exit(EXIT_FAILURE);
  }

  curnode = curnode->xmlChildrenNode;
  do {
    if (!xmlStrcmp(curnode->name, (const xmlChar *)"channel"))
    {
      get_title(doc, curnode);
    }
    curnode = curnode->next;
  }while(curnode);

  xmlFreeDoc(doc);
}

void get_title(xmlDocPtr doc, xmlNodePtr curnode)
{
  curnode = curnode->xmlChildrenNode;
  do {
    if (!xmlStrcmp(curnode->name, (const xmlChar *)"title"))
    {
      xmlChar *value = xmlNodeListGetString(doc, curnode->xmlChildrenNode, 1);
      fprintf(stdout, "%s\n", value);
      xmlFree(value);
    } 
    curnode = curnode->next;
  }while(curnode); 
}
@slayerlab
Copy link
Author

slayerlab commented Aug 29, 2016

Instalação da libxml2:
[Linux]

apt-get install libxml2 libxml2-dev

[Mac OSX]

brew install libxml2

PS: Se houver essas, então provavelmente também está disponível a libxml2-doc,
mas a documentação está bastante incompleta, não vale a pena instalar.

Todos os exemplos de scripts podem ser compilados normalmente:
$ gcc -o <output> <source.c> $(xml2-config --libs --cflags)

Documentação superficial (oficial), pode ser encontrado em:
http://xmlsoft.org/html/libxml-tree.html

Rascunho de uma documentação incompleta (oficial):
http://xmlsoft.org/tutorial/xmltutorial.pdf

Qualquer coisa, reportar para: pr0j3kt . slayerowner [at] gmail [dot] com

@slayerlab
Copy link
Author

Dropping this as public 2 years later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment