📚 Capítulo do curso: [12 — Cadeias de Caracteres (Strings),
e Segurança OWASP/CERT C](../../espec_sistemas_c/topicos/12_strings_manipulacao_e_seguranca_owasp.html) da [Especialização em Sistemas com C](../../espec_sistemas_c/index.html). Leia o capítulo antes de começar.
Parser de frases com validação de limites de buffer e funções seguras de string, seguindo as recomendações CERT C e OWASP.
| ⏱️ Tempo estimado | 📦 Você vai entregar | ✅ Pronto quando |
|---|---|---|
| 50 min | main.c + entrada.txt |
9 vogais, 5 palavras e o resumo truncado em 19 caracteres |
fgets em vez de gets/scanf("%s")).char terminados em \0) usando funções de <string.h>.char manualmente.strcpy sem limite é uma das causas mais comuns de vulnerabilidades de segurança em C.Você está construindo o módulo de entrada de texto de um analisador léxico simplificado. Ele lê uma frase digitada pelo usuário e produz um pequeno relatório: quantos caracteres, vogais e palavras a frase tem, além de um “resumo” truncado da frase para caber em um campo de tamanho fixo (como um campo de formulário web ou uma coluna de banco de dados).
Por que fgets e nunca gets/scanf("%s") sem limite? A função gets foi removida do padrão C11 justamente por não ter como limitar quantos bytes ela escreve — qualquer entrada maior que o buffer de destino sobrescreve memória adjacente na pilha (um buffer overflow clássico, catalogado como CWE-120 e coberto pela regra CERT C STR31-C: “Guarantee that storage for strings has sufficient space for character data and the null terminator”). O mesmo problema existe com scanf("%s", buffer) sem um especificador de largura. fgets(buffer, sizeof(buffer), stdin) resolve isso porque sempre recebe o tamanho do buffer de destino como parâmetro, nunca escrevendo além dele.
O programa (main.c) deve:
fgets, respeitando o tamanho de um buffer fixo.\n residual que o fgets deixa no final da string.strlen), vogais (usando strchr para checar se cada caractere está no conjunto "aeiou") e palavras da frase.strncpy com terminação manual (ou snprintf), nunca strcpy puro.| ID | Requisito |
|---|---|
| RF01 | O programa deve compilar sem nenhum aviso com gcc -Wall -Wextra -Wpedantic -std=c17. |
| RF02 | Deve ler a entrada com fgets — o uso de gets ou scanf("%s", ...) sem limite de tamanho é proibido. |
| RF03 | Deve remover o caractere \n residual deixado pelo fgets ao final da string lida. |
| RF04 | Deve contar corretamente caracteres, vogais e palavras da frase, usando pelo menos 3 funções de <string.h> (por exemplo strlen, strchr, strncpy). |
| RF05 | Deve copiar os primeiros caracteres da frase para um buffer menor de forma seguramente truncada (strncpy/snprintf), garantindo que a cópia nunca ultrapasse o tamanho do buffer de destino e sempre termine em \0. |
graph LR
A["fgets(stdin)\nleitura segura"] --> B["remover quebra\nde linha residual"]
B --> C["contar_vogais() /\ncontar_palavras()\n(string.h)"]
C --> D["copia truncada segura\n(strncpy + terminador manual)"]
D --> E["Relatorio final"]
style A fill:#0284c7,stroke:#38bdf8,color:#ffffff
style D fill:#0d9488,stroke:#2dd4bf,color:#ffffff
style E fill:#059669,stroke:#34d399,color:#ffffff
O mesmo do Miniprojeto 01: gcc 8 ou mais novo. No Windows, rode chcp 65001 se os emojis aparecerem quebrados.
🎯 Objetivo: partir de um código que compila e roda, antes de escrever qualquer linha.
⌨️ Faça: crie a pasta c_mini_12, salve o código abaixo como main.c e rode:
gcc -Wall -Wextra -Wpedantic -std=c17 main.c -o app
./app # Linux/macOS
.\app.exe # Windows (PowerShell)
📄 src/esqueleto.c
/*
* Miniprojeto 12: Analisador Lexico de Strings Seguro (CERT C / OWASP)
* Capitulo 12: strings, string.h, seguranca CERT C / OWASP.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define TAM_BUFFER 200
#define TAM_RESUMO 20
int contar_vogais(const char frase[]) {
(void)frase; // stub: apague esta linha ao implementar
// TODO 1: percorra a string ate o '\0' e conte quantos caracteres
// (em minusculo, veja tolower) estao em "aeiou" (dica: strchr).
return 0;
}
int contar_palavras(const char frase[]) {
(void)frase; // stub: apague esta linha ao implementar
// TODO 2: percorra a string contando transicoes de "fora de palavra"
// para "dentro de palavra" (use isspace para detectar espacos).
return 0;
}
void remover_quebra_de_linha(char frase[]) {
(void)frase; // stub: apague esta linha ao implementar
// TODO 3: use strlen para achar o ultimo caractere; se for '\n',
// substitua-o por '\0'.
}
int main(void) {
char frase[TAM_BUFFER];
printf("Digite uma frase (ate %d caracteres): ", TAM_BUFFER - 1);
if (fgets(frase, sizeof(frase), stdin) == NULL) {
printf("Erro ao ler entrada.\n");
return EXIT_FAILURE;
}
remover_quebra_de_linha(frase);
printf("\nFrase lida: \"%s\"\n", frase);
// TODO 4: calcule e imprima strlen(frase), contar_vogais(frase) e
// contar_palavras(frase).
// TODO 5: declare char resumo[TAM_RESUMO]; e copie com seguranca os
// primeiros (TAM_RESUMO - 1) caracteres
// de "frase" para "resumo" usando strncpy + terminador manual, e
// imprima o resultado. NUNCA use strcpy(resumo, frase) aqui.
return EXIT_SUCCESS;
}
✅ Checkpoint: zero avisos do gcc, e a saída é:
🖥️ Saída esperada:
Digite uma frase (ate 199 caracteres):
Frase lida: "Ola mundo da linguagem C
"
Ao rodar, digite Ola mundo da linguagem C e Enter. A saída acima é a do teste automático, com a frase enviada por redirecionamento.
👀 Repare na aspa de fechamento na linha de baixo: é o \n que o fgets guardou dentro da string. O remover_quebra_de_linha ainda é um stub.
💡 As linhas
(void)frase;silenciam o aviso de parâmetro não usado enquanto a função é um stub. Apague cada uma ao implementar a função.
\n residual (TODO 3)🎯 Objetivo: limpar a string lida pelo fgets com segurança, inclusive quando ela está vazia.
⌨️ Faça: size_t tamanho = strlen(frase); e, se tamanho > 0 && frase[tamanho - 1] == '\n', troque esse caractere por '\0'.
✅ Checkpoint: Frase lida: "Ola mundo da linguagem C", com as aspas na mesma linha.
🧯 Se der errado: sem o teste tamanho > 0, uma string vazia faria frase[-1], uma leitura fora do buffer. É o tipo de erro que a regra CERT C STR31-C combate.
🎯 Objetivo: percorrer uma string até o '\0' usando <string.h> e <ctype.h>.
⌨️ Faça:
contar_vogais: para cada caractere, tolower((unsigned char) c) e strchr("aeiou", c) != NULL.contar_palavras: uma flag dentro_de_palavra. Um espaço (isspace) zera a flag, e o primeiro caractere não espaço depois dela conta uma palavra nova.main, troque a linha Frase lida por um bloco Resultado da analise com caracteres (strlen, impresso com %zu), vogais e palavras.✅ Checkpoint: Caracteres: 24, Vogais: 9, Palavras: 5.
🧯 Se der errado:
(unsigned char) não é enfeite: passar um char negativo (como um acento em Latin-1) para tolower ou isspace é comportamento indefinido.🎯 Objetivo: copiar para um buffer menor sem jamais escrever além dele.
⌨️ Faça: declare char resumo[TAM_RESUMO];, chame strncpy(resumo, frase, sizeof(resumo) - 1); e, sempre, resumo[sizeof(resumo) - 1] = '\0';. Imprima o resumo.
✅ Checkpoint: Resumo (max 19 chars): "Ola mundo da lingua".
🧯 Se der errado: com uma frase longa, a saída mostra lixo depois do resumo: faltou o terminador manual. O strncpy não escreve '\0' quando a origem é maior que o limite.
🎯 Objetivo: validar a saída completa e o código de retorno.
⌨️ Faça: crie entrada.txt na pasta do projeto, com o conteúdo abaixo, e rode o programa com a entrada redirecionada:
📄 tests/main.entrada.txt
Ola mundo da linguagem C
./app < entrada.txt # Linux/macOS
Get-Content entrada.txt | .\app.exe # Windows (PowerShell)
✅ Checkpoint: com a entrada redirecionada o terminal não ecoa o que seria digitado, então as respostas não aparecem depois das perguntas. A saída tem que bater com esta, e o código de retorno é 0 (echo $? ou echo $LASTEXITCODE):
🖥️ Saída esperada:
====================================================
⚡ Analisador Lexico de Strings Seguro (CERT C / OWASP)
====================================================
Digite uma frase (ate 199 caracteres):
--- Resultado da analise ---
Frase lida: "Ola mundo da linguagem C"
Caracteres: 24
Vogais: 9
Palavras: 5
Resumo (max 19 chars): "Ola mundo da lingua"
====================================================
Só abra o gabarito depois de chegar até aqui:
src/main.c📄 src/main.c
/*
* Miniprojeto 12: Analisador Lexico de Strings Seguro (CERT C / OWASP)
* Especializacao em Engenharia de Sistemas em C
* Capitulo 12: strings, string.h, seguranca CERT C / OWASP.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define TAM_BUFFER 200
#define TAM_RESUMO 20
int contar_vogais(const char frase[]) {
int total = 0;
for (int i = 0; frase[i] != '\0'; i++) {
char c = (char) tolower((unsigned char) frase[i]);
if (strchr("aeiou", c) != NULL) {
total++;
}
}
return total;
}
int contar_palavras(const char frase[]) {
int total = 0;
int dentro_de_palavra = 0;
for (int i = 0; frase[i] != '\0'; i++) {
if (isspace((unsigned char) frase[i])) {
dentro_de_palavra = 0;
} else if (!dentro_de_palavra) {
dentro_de_palavra = 1;
total++;
}
}
return total;
}
void remover_quebra_de_linha(char frase[]) {
size_t tamanho = strlen(frase);
if (tamanho > 0 && frase[tamanho - 1] == '\n') {
frase[tamanho - 1] = '\0';
}
}
int main(void) {
char frase[TAM_BUFFER];
char resumo[TAM_RESUMO];
printf("====================================================\n");
printf(" ⚡ Analisador Lexico de Strings Seguro (CERT C / OWASP)\n");
printf("====================================================\n");
printf("Digite uma frase (ate %d caracteres): ", TAM_BUFFER - 1);
if (fgets(frase, sizeof(frase), stdin) == NULL) {
printf("Erro ao ler entrada.\n");
return EXIT_FAILURE;
}
remover_quebra_de_linha(frase);
size_t comprimento = strlen(frase);
int vogais = contar_vogais(frase);
int palavras = contar_palavras(frase);
printf("\n--- Resultado da analise ---\n");
printf("Frase lida: \"%s\"\n", frase);
printf("Caracteres: %zu\n", comprimento);
printf("Vogais: %d\n", vogais);
printf("Palavras: %d\n", palavras);
/*
* Copia segura dos primeiros (TAM_RESUMO - 1) caracteres para um buffer
* menor. strncpy NUNCA escreve alem de "sizeof(resumo) - 1" bytes de
* conteudo, mas (diferente de strcpy) tambem NAO garante o '\0' final
* se "frase" for maior ou igual ao buffer -- por isso terminamos a
* string manualmente na linha seguinte.
*
* O que strcpy(resumo, frase) faria de errado: se "frase" tiver mais
* caracteres que "resumo" comporta, strcpy escreve alem do fim de
* "resumo", corrompendo memoria adjacente na pilha -- um buffer
* overflow classico (CWE-120 / CERT C STR31-C).
*/
strncpy(resumo, frase, sizeof(resumo) - 1);
resumo[sizeof(resumo) - 1] = '\0';
printf("Resumo (max %d chars): \"%s\"\n", TAM_RESUMO - 1, resumo);
printf("====================================================\n");
return EXIT_SUCCESS;
}
Por que isso importa: strncpy é frequentemente mal utilizada — muita gente esquece que ela não garante terminação em \0 quando a origem é maior ou igual ao destino. A linha resumo[sizeof(resumo) - 1] = '\0'; não é opcional: sem ela, printf("%s", resumo) poderia ler além do buffer, outro tipo de falha de segurança (leitura fora dos limites).
Adicione uma função contar_consoantes (simetria com contar_vogais) e uma validação de “campo de formulário”: se strlen(frase) ultrapassar, por exemplo, 50 caracteres, imprima um aviso "Entrada excede o limite permitido para este campo" em vez de processá-la — simulando a validação de entrada (input validation) recomendada pelo OWASP para qualquer campo de texto vindo do usuário, antes mesmo de tentar copiar ou processar os dados.
✅ Checkpoint: Ola mundo da linguagem C tem 11 consoantes (9 vogais + 11 consoantes = 20 letras; os outros 4 caracteres são espaços). Uma frase de 51 caracteres mostra o aviso de limite e não é processada.
📋 Projetos do curso de C | 📚 Teoria do Capítulo 12 | 🗂️ Hub de projetos