#include <stdio.h>
#include "input.h"
#include "password.h"
// Programa "ScapeRoom" que conté errors intencionats.
// L'objectiu de l'usuari és arreglar el codi per tal que funcioni bé.
// Conté les explicacions suficients perquè tothom ho pugui resoldre (Després de
// tot el codi)
/**
* Solucionat per Aleix Mariné-Tena
*
* Versió exhaustiva de la solució on s'intenten arreglar tots els errors,
* es preveuen alguns casos extrems comuns en C al manipular input d'usuari i
* s'apliquen algunes opinions en l'estil del codi.
*
* Canvis aplicats:
* - Afegida llibreria per a llegir input d'usuari. L'error que soluciona es que
* * 1.- scanf("%d", &pass); i similar es insegur per no especificar la mida
* dels caràcters esperats, causant un possible stack overflow.
* * 2.- La seva trucada té side-effects: Pot deixar caràcters al buffer que
* entorpeixen lectures subseqüents i poden arribar a trencar l'execució.
* * 3.- scanf("%s", nom); no controla correctament els espais, pel que dels
* noms compostos només es llegiria el nom.
* La llibreria internament fa servir fgets i controla errors típics de
* caràcters vàlids, rangs, gestió del buffer d'input i casos extrems amb les
* mides dels buffers.
* - La mida dels buffers d'input passen a ser responsabilitat de la llibreria
* d'input pel que s'omet la mida i es fan servir punters.
* - Un password es redefineix com un string (char*) ja que el text lliure és el
* tipus habitual per un password. En la mateixa linia, es fa servir strcmp
* per a comprovar la seva correctesa.
* - scanf("%d, nom); és obviament incorrecte i caldria llegir un string
* - L'espai extra del scanf(" %c", &genere); és incorrecte i cal llegir només
* un caràcter.
* - Eliminat el cas else de la lectura de gènere ja que la funció de lectura
* no continua amb la seva execució fins que no s'introdueixi un dels
* caràcters acceptats.
* - Canviat case 'X' del gènere incorrecte per 'N'.
* - Truncades les línies de més de 80 caràcters, assumint que és aquest l'estil
* del codi.
* - Unificades la declaració de variables i s'alinien els comentaris.
* - Es mou l'explicació a un fitxer independent de documentació
* "explicació.md".
* - Es canvia el password per un més adequat, es treu el password hard-coded
* del codi i s'intercanvia per un fitxer de text amb el password en text clar
* i una funció específica read_password_file() que el llegeix.
* - Substituida la ç trencada del string pel seu equivalent unicode ja que no
* es veia correctament al terminal del onlineGDB (ni probablement en cap
* altre).
*
* Llibreria de la solució basada en la llibreria "libinput" de:
* https://github.com/game-implementations/Battleship
*
*
*/
int main() {
char *nom, // Variable per guardar el nom (cadena de caràcters)
*pass, // Variable per comprovar la contrasenya
genere; // Variable per guardar el gènere introduït
// Missatge inicial de reptec
printf("=============================================\n");
printf(" 🚀 BENVINGUT AL PRIMER REPTE DE CODE URV 🚀\n");
printf("=============================================\n\n");
printf("Seràs capac\u0327 d'arreglar el codi?\n");
printf("👉 Has d'aconseguir que el programa et doni la benvinguda\n");
printf(" personalitzada a l'associació.\n\n");
// Opció per demanar una pista
printf("Vols una pista? Escriu la contrasenya (o 0 per continuar): ");
pass = readString();
// Comprovar que la contrasenya és correcta
char* password = read_password_file();
if (password != NULL)
{
if (strcmp(pass, password) == 0) {
printf("\n🔑 PISTA: Revisa bé els formats de scanf, printf i les \
comparacions en el switch...\n\n");
} else if (strcmp(pass, "0") != 0) {
printf("\n❌ Contrasenya incorrecta! Continua sense pista...\n\n");
}
}
else
{
printf("El programa no ha pogut llegir el fitxer de password, pel que\
no es pot comprovar.");
}
// Demanem el nom de l'usuari
printf("Introdueix el teu nom: ");
nom = readString();
// Demanem el gènere
printf("Introdueix el teu gènere (M = masculí, F = femení, N = no binari):\
");
char valid_genres[] = {'M', 'F', 'N'};
genere = readCharInSet(valid_genres, 3);
// Mostrem un missatge de benvinguda personalitzat amb switch-case
switch (genere) {
case 'P':
printf("Benvingut a code URV, %s\n", nom);
break;
case 'O':
printf("Benvinguda a code URV, %s\n", nom);
break;
case 'N': //🔑 PISTA ERROR INTENCIONAL: hauria de ser 'N'
printf("Benvingud@ a code URV, %s\n", nom);
break;
}
return 0;
}
#include "input.h"
void discard_rest_of_line(void) {
int c;
while ((c = fgetc(stdin)) != '\n' && c != EOF) {}
}
char readChar()
{
char* readInput = (char*) calloc(100, sizeof(char));
do
{
if (fgets(readInput, 100, stdin) == NULL)
{
continue;
}
}
while (readInput[1] != '\n');
return readInput[0];
}
bool isCharInSet(char letter, char* characterSet, int numCharacterSet)
{
for (int i = 0; i < numCharacterSet; i++)
{
if (characterSet[i] == letter)
{
return true;
}
}
return false;
}
char readCharInSet(char* characterSet, int numCharacterSet)
{
char readCharacter;
bool hasSetError = false;
do
{
if (hasSetError)
{
printf("You have given a char out of range. Try again.\n");
}
readCharacter = readChar();
hasSetError = !isCharInSet(readCharacter, characterSet, numCharacterSet);
}
while (hasSetError);
return readCharacter;
}
char *readString(void) {
char buf[READSTR_BUFSIZE];
for (;;) {
if (fgets(buf, sizeof buf, stdin) == NULL) {
// EOF or error — just keep waiting for valid input
clearerr(stdin);
continue;
}
// If no newline, input was too long — discard rest of the line
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] != '\n') {
discard_rest_of_line();
} else if (len > 0) {
buf[len - 1] = '\0'; // strip newline
}
// Allocate and return
char *result = (char *) malloc(strlen(buf) + 1);
if (!result) {
// If allocation fails, prompt again
continue;
}
strcpy(result, buf);
return result;
}
}
void* memcpy(void* dest, const void* src, size_t n)
{
// Initialize pointers with implicit size of a byte
char* src_byte = (char*) src;
char* dest_byte = (char*) dest;
for (unsigned int i = 0; i < n; i++)
{
dest_byte[i] = src_byte[i];
}
return dest;
}
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
// Size of the buffer when reading input
#define READINT_BUFSIZE 64 // plenty for any int (+ spaces, sign, newline)
#define READSTR_BUFSIZE 256 // plenty for typical input
/**
* Flushes the stdin buffer .
*/
void discard_rest_of_line(void);
/**
* Reads a char from user.
*
* @return Read char.
*/
char readChar();
/**
* Reads a char from user input in a given set. While the data introduced is not a char or is not in the set, tries
* again. The set goes from the minimum char to the maximum char including both of them. The range uses the ASCII
* code.
*
* @param characterSet Set of characters that we accept as valid.
* @param numCharacterSet Number of characters in the accepted character set.
* @return Read char.
*/
char readCharInSet(char* characterSet, int numCharacterSet);
/**
* Returns true if the supplied char is in the set, false otherwise.
*
* @param letter Character that we are querying.
* @param characterSet Set of characters that we accept as valid.
* @param numCharacterSet Number of characters in the set that we accept as valid.
* @return True if the char is in the set, false otherwise.
*/
bool isCharInSet(char letter, char* characterSet, int numCharacterSet);
/**
* Reads a line from stdin, discards newline
* and repeats until a non-empty string is read.
* Always returns an allocated string (never NULL).
* @return Pointer to a buffer containing the read string
*/
char *readString(void);
/**
* Copies n bytes from source to destination.
*
* @param dest Pointer to the destination the copy.
* @param src Pointer to the source of the data to copy.
* @param n Number of bytes to copy.
* @return Destination address.
*/
void* memcpy(void* dest, const void* src, size_t n);
## EXPLICACIÓ:
- `printf("text", variables...);`
Serveix per mostrar text per pantalla. El text pot incloure "formats" especials:
* `%s` = imprimir una cadena de caràcters (string)
* `%c` = imprimir un caràcter
* `%d` = imprimir un enter (int)
- `scanf("format", &variable);`
Serveix per llegir dades que l'usuari introdueix pel teclat.
El "format" indica quin tipus de dada s’espera.
És important passar l'**adreça** de la variable (amb `&`)
per a que `scanf` hi pugui guardar el valor.
**Exemple correcte per llegir un nom:**
```c
scanf("%s", nom); // perquè "nom" és un array de chars
CODE URV MOLA
#include "password.h"
char *read_password_file(void) {
const char *filename = "PASSWORD";
FILE *fp = fopen(filename, "r");
if (!fp) {
perror("Error opening PASSWORD file");
return NULL;
}
// Seek to the end to know the size
if (fseek(fp, 0, SEEK_END) != 0) {
perror("Error seeking file");
fclose(fp);
return NULL;
}
long size = ftell(fp);
if (size < 0) {
perror("Error getting file size");
fclose(fp);
return NULL;
}
rewind(fp);
// Allocate buffer (+1 for null terminator)
char *buffer = malloc(size + 1);
if (!buffer) {
perror("Memory allocation failed");
fclose(fp);
return NULL;
}
size_t read_size = fread(buffer, 1, size, fp);
buffer[read_size] = '\0'; // Null-terminate
if (ferror(fp)) {
perror("Error reading file");
free(buffer);
fclose(fp);
return NULL;
}
fclose(fp);
return buffer;
}
#include <stdio.h>
#include <stdlib.h>
char *read_password_file(void);