Há alguns meses, Dare Obasanjo notou uma breve troca de mensagens com meu amigo Jon Galloway e eu tivemos no Twitter. Infelizmente, o Twitter torna excepcionalmente difícil acompanhar as conversas, mas Dare descreve a essência da conversa em Desenvolvedores, usar bibliotecas não é um sinal de fraqueza:
O problema que Jeff estava tentando resolver é o seguinte como permitir um subconjunto de tags HTML e, ao mesmo tempo, remover todos os outros HTML para evitar ataques de XSS (cross site scripting). O problema com a abordagem de Jeff, que foi apontado nos comentários por muitas pessoas, inclusive Simon Willison, é que o uso de regexes para filtrar a entrada de HTML dessa forma pressupõe que o senhor obterá um HTML razoavelmente bem formado. O problema com essa abordagem, que muitos desenvolvedores descobriram da maneira mais difícil, é que o senhor também precisa se preocupar com HTML malformado devido às políticas liberais de análise de HTML de muitos navegadores modernos da Web. Portanto, para usar essa abordagem, o senhor precisa fazer engenharia reversa de todas as peculiaridades de análise de HTML dos navegadores comuns se não quiser acabar armazenando HTML que parece seguro, mas que, na verdade, contém um exploit. Portanto, para utilizar essa abordagem, Jeff realmente deveria estar procurando usar um analisador de HTML completo, como o SgmlReader ou o Beautiful Soup, em vez de expressões regulares.
O que é triste é que Jeff Atwood não é o primeiro nem será o último programador a pensar: “É apenas sanitização de HTML, será que é tão difícil assim?”. Há muitas listas de Principais erros de validação de HTML que mostram como é complicado obter a solução correta para esse problema aparentemente trivial. Além disso, é triste notar que, apesar de sua experiência recente, Jeff Atwood ainda argumenta que ele prefere cometer seus próprios erros a herdar cegamente os erros dos outros como justificativa para continuar reinventando a roda no futuro. Isso é lamentável, pois essa é uma atitude ruim para um desenvolvedor de software profissional.
Minha resposta?
@jongalloway o senhor tem razão, codificar é difícil. Vamos às compras!
– Jeff Atwood (@codinghorror) 31 de agosto de 2008
Atitude ruim? Acho que isso é uma questão de perspectiva.
(A fase “programar é difícil, vamos fazer compras!” é um clone de neve. Como sempre, Language Log nos dá cobertura. Ironicamente, mais tarde tivemos um breve encontro com a “própria” Barbie Consultora no Stack Overflow – que talvez o senhor conheça do reddit. Não há nenhum vestígio dela no SO, mas no que diz respeito ao griefing, foi bastante benigno e até mesmo indiscutivelmente dentro do tópico).
No desenvolvimento do Stack Overflow, determinei logo no início que estaríamos usando Markdown para inserir perguntas e respostas no sistema. Infelizmente, Markdown permite que os usuários misturem HTML na marcação. É o parte da especificação e tudo mais. Na verdade, eu gostaria que não fosse – um dos grandes atrativos de linguagens de pseudo-marcação como BBCode é que eles têm nada em comum com o HTML e, portanto, a limpeza da entrada torna-se trivial. Os usuários têm duas opções:
- Inserir pseudo-marcação aprovada.
- Pergunta capciosa. Não há outra opção!
Com o BBCode, se o usuário digitar HTML, o senhor o elimina com extremo preconceito – ele é codificado, sem exceções. É fácil. Não é necessário pensar e quase nenhum código.
Como usamos Markdown, não temos esse luxo. Quer o senhor goste ou não, agora estamos no desagradável e brutal negócio de distinguir a “boa” marcação HTML da “má” marcação HTML. Isso é difícil. Realmente difícil. Dare e Jon têm razão em questionar a competência e talvez até mesmo a sanidade mental de qualquer desenvolvedor que tenha decidido, por vontade própria, enfrentar esse problema específico.
Mas a questão é a seguinte: entender profundamente a sanitização de HTML é uma parte essencial do meu negócio. Para mim, a inserção de markdown pelos usuários não é apenas uma pequena caixa de seleção em uma matriz de recursos, é literalmente toda a base sobre a qual nosso site foi construído.
Aqui está um teste pop de 2001. Veja como o senhor se sai.
- Reutilização de código é:
- Bom
- Ruim
- Reinventar a roda é:
- Bom
- Ruim
- A Síndrome do Não Inventado Aqui é:
- Bom
- Ruim
Tenho certeza de que a maioria dos desenvolvedores está praticamente pulando uns sobre os outros na ânsia de responder neste momento. De é claro a reutilização de código é boa. De curso reinventar a roda é ruim. De é claro a síndrome do “não inventado aqui” é ruim.
Exceto quando não é.
Se for uma função comercial essencial, faça-a você mesmo, não importa o que aconteça.
Escolha suas principais competências e metas de negócios e faça-as internamente. Se o senhor for uma empresa de software, escrever um código excelente é a forma de obter sucesso. Vá em frente e terceirize o refeitório da empresa e a duplicação de CD-ROMs. Se o senhor for uma empresa farmacêutica, crie um software para pesquisa de medicamentos, mas não crie seu próprio pacote de contabilidade. Se o senhor for um serviço de contabilidade na Web, crie seu próprio pacote de contabilidade, mas não tente criar seus próprios anúncios em revistas. Se o senhor tiver clientes, nunca terceirize o atendimento ao cliente.
Ser um desenvolvedor “profissional”, se é que isso realmente existe – ainda tenho minhas dúvidas -, não significa escolher bibliotecas de terceiros para cada tarefa de programação possível que o senhor encontrar. Tampouco significa escrever tudo cegamente por conta de um senso equivocado de dever ou da percepção de que é isso que os programadores gonzo e hardcore fazem. Em vez disso, os desenvolvedores experientes aprendem quais são suas principais funções de negócios e escrevem qualquer software que considerem necessário para executar essas funções extraordinariamente bem.
Se me arrependo de ter passado uma semana inteira criando um conjunto de funções de sanitização de HTML para o Stack Overflow? Nem mesmo um pouco. Há muitas soluções de sanitização fora do ecossistema .NET, mas muito poucas para C# ou VB.NET. Eu contribuí com o código principal de volta para a comunidadepara que os futuros aventureiros do .NET possam usar nosso código como um guia (ou sinal de alerta, dependendo da perspectiva do senhor) em sua própria jornada. Eles podem aprender com a rotina simples e comprovada que escrevemos e continuamos a usar no Stack Overflow todos os dias.
(Infelizmente, esse código ultrapassou o site refactormycode, por isso reimprimi a versão final aqui; considere-o licenciado pelo MIT).
private static Regex _tags = new Regex("<[^>]*(>|$)", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled); private static Regex _whitelist = new Regex(@" ^</?(b(lockquote)?|code|d(d|t|l|el)|em|h(1|2|3)|i|kbd|li|ol|p(re)?|s(ub|up|trong|trike)?|ul)>$| ^<(b|h)r\s?/?>$", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace); private static Regex _whitelist_a = new Regex(@" ^<a\s href=""(\#\d+|(https?|ftp)://[-a-z0-9+&@#/%?=~_|!:,.;\(\)]+)"" (\stitle=""[^""<>]+"")?\s?>$| ^</a>$", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace); private static Regex _whitelist_img = new Regex(@" ^<img\s src=""https?://[-a-z0-9+&@#/%?=~_|!:,.;\(\)]+"" (\swidth=""\d{1,3}"")? (\sheight=""\d{1,3}"")? (\salt=""[^""<>]*"")? (\stitle=""[^""<>]*"")? \s?/?>$", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace); /// <summary> /// sanitize any potentially dangerous tags from the provided raw HTML input using /// a whitelist based approach, leaving the "safe" HTML tags /// CODESNIPPET:4100A61A-1711-4366-B0B0-144D1179A937 /// </summary> public static string Sanitize(string html) { if (String.IsNullOrEmpty(html)) return html; string tagname; Match tag; // match every HTML tag in the input MatchCollection tags = _tags.Matches(html); for (int i = tags.Count - 1; i > -1; i--) { tag = tags[i]; tagname = tag.Value.ToLowerInvariant(); if(!(_whitelist.IsMatch(tagname) || _whitelist_a.IsMatch(tagname) || _whitelist_img.IsMatch(tagname))) { html = html.Remove(tag.Index, tag.Length); System.Diagnostics.Debug.WriteLine("tag sanitized: " + tagname); } } return html; }
Sinceramente, não sou um desenvolvedor muito bom. Não sou tão talentoso quanto competente e barulhento. Comece a escrever e a falar e o o senhor também pode ser barulhento. Mas vou lhe dizer uma coisa: ao optar por travar essa batalha do desinfetante HTML, eu ganhei as cicatrizes da experiência. Não preciso acreditar na palavra de ninguém, não preciso confiar em “bibliotecas”. Posso analisar o código, examinar a entrada e a saída e prever exatamente quais tipos de problemas podem surgir. Tenho uma compreensão profunda dos riscos, das armadilhas e das vantagens e desvantagens da sanitização de HTML. e vulnerabilidades de script entre sites.
Como Richard Feynman escreveu de forma tão famosa em seu último quadro negro, o que eu não posso criar, eu não entendo.
Esse é exatamente o tipo de experiência em programação de que preciso para ficar de olho no Stack Overflow, e eu não a trocaria por nada. Talvez o senhor não esteja criando um site que dependa da inserção de marcações pelos usuários, portanto, talvez tome uma decisão diferente da minha. Mas, certamente, há algo, alguma competência comercial essencial, tão importante que o o senhor se sente compelido a criá-la por conta própria, mesmo que isso signifique cometer seus próprios erros.
Programar é difícil. Mas isso não significa que o senhor deva sempre comprar bibliotecas de terceiros em vez de escrever código. Se for uma função comercial essencial, escreva o código você mesmo, Não importa o que aconteça. Se outros programadores não entenderem por que é tão importante que o senhor se sente e escreva esse trecho de código, bem, isso é problema deles.
Eles provavelmente estão ocupados demais fazendo compras para entender.