Forum: PC-Programmierung Formular auslesen in C


von ed. (Gast)


Lesenswert?

hi,
habe folgendes problem, habe in einem Unterprogramm folgende Zeilen:
1
  
2
char *test_user = getKeyVal("User", BUFFER);
3
char *test_pass = getKeyVal("Password", BUFFER);
BUFFER ist ein char-array,
die dazugehörige Funktion ist:
1
char* getKeyVal(char* Keyword,const char* Key)
2
{
3
  char* newKeyword = (char*)malloc((strlen(Keyword) + 2)*sizeof(char));
4
    
5
  strcpy(newKeyword,Keyword);
6
  strcat(newKeyword,"=\0");
7
  char* value = strstr(Key,newKeyword);
8
  value += strlen(newKeyword);
9
  
10
  int pos = strcspn(value,"&\r");
11
  *(value + pos) = '\0';
12
  
13
  free(newKeyword);
14
  return value;  
15
}

lese ich erst User und dann Passwort aus, erhalte ich für das Passwort 
einen falschen Wert, lese ich jedoch erst das Passwort und dann den User 
aus, erhalte ich für beides die richtigen Werte...
woran liegt das, an der dynamischen Speicherreservierung? ist diese 
verkehrt? das Programm läuft auf einem uC, deswegen kann ich z.b. 
getenv() nicht benutzen...

gruß

von Walter (Gast)


Lesenswert?

den ersten Fehler den ich sehe:
newkeyword ist ein Byte zu kurz

Gruß
Walter

von Francesco N. (franceso-)


Lesenswert?

> char *test_user = getKeyVal("User", BUFFER);
> char *test_pass = getKeyVal("Password", BUFFER);
> [/C]
> BUFFER ist ein char-array,
> die dazugehörige Funktion ist:
>
1
> 
2
> char* getKeyVal(char* Keyword,const char* Key)
3
> {
4
>   char* value; char len; len=strlen(Keyword);
5
    for(value=Key;value&&*value&&(value=strstr(value,Keyword));value++) 
6
     if(value[len]!='='||len&&value[-1]!='&') continue; else break;
7
    if(!value) return ""; // oder NULL oder 0, je nach code.
8
    value+=++len;
9
>   value[strcspn(value,"&\r")]='\0';
10
>   return value;
11
> }
12
>
>

von Francesco N. (franceso-)


Lesenswert?

Das wird es wohl gewesen sein.
du kannst auch
strcat(newKeyword,"="); schreiben, dann stimmt es.

von ed. (Gast)


Lesenswert?

Hi Francesco,

hab den Ansatz mit strcat(newKeyword,"=") mal ausprobiert. Es ändert 
sich nichts am Verhalten, ist mir ein Rätsel. Lass ich erst nach 
Passwort suchen und dann nach User, gibt es keine Probleme. Suche ich 
erst nach User und dann nach Passwort, bekomme ich für das PW einen 
falschen Wert.

gruß

von Karl H. (kbuchegg)


Lesenswert?

> den ersten Fehler den ich sehe:
> newkeyword ist ein Byte zu kurz

Ist er nicht.
Das
  strcat(newKeyword,"=\0");
ist zwar ungewöhnlich, verlängert den String aber
trotzdem nur um 1 Zeichen.

> lese ich erst User und dann Passwort aus, erhalte ich für das
> Passwort einen falschen Wert, lese ich jedoch erst das Passwort
> und dann den User aus, erhalte ich für beides die richtigen
> Werte...

Lass mich raten:
In deinem BUFFER kommt zuerst der User und dann das Passwort.

  *(value + pos) = '\0';

Das würde ich nicht tun. Das verändert den Originalstring.
Wenn du zuerst nach dem User suchst, und User im String tatsächlich
zuerst kommt, führt das dazu, dass das weiter hinten liegende
Passwort hier:
  char* value = strstr(Key,newKeyword);

nicht mehr gefunden werden kann, weil dir das '\0' den
String nach dem User terminiert.
Dadurch, dass du den Returnwert auch nicht auf NULL
überprüfst, passieren dann halt seltsame Dinge, wenn
strstr nicht fündig wird.

von Francesco N. (franceso-)


Lesenswert?

  strcat(newKeyword,"=\0");
ergänzt den String mit 2 Bytes und fügt ein terminierendes \0 hinzu.
Insgesamt 3 Bytes, weniger das terminierende Null-Byte das vom 1en Byte
überschrieben wird, also 2 Bytes mehr.

von Karl H. (kbuchegg)


Lesenswert?

>  strcat(newKeyword,"=\0");
> ergänzt den String mit 2 Bytes

red keinen Unsinn.
strcat holt sich vom 2. String solange Character
bis es auf ein '\0' Zeichen trifft.

auch bei

   strcat( newKeyword, "=\0abcdefghijkl" );

wird nur das erste '=' berücksichtigt, weil
das nächste Zeichen ein '\0' ist. Alles dahinter
interessiert strcat nicht mehr. Sobald es auf
ein '\0' trifft, ist für strcat() und alle
anderen str... Funktionen der String zu Ende.

von Francesco N. (franceso-)


Lesenswert?

Nein, bis es auf ein richtiges NULL trifft.
Probier es aus,statt strcpy cansst du auch strcat benutzen.

char buff[20]="";char*p;
strcpy(buff,"eins\0zwei\0drei\0");
for(p=buff;*p;p+=strlen(p)+1) puts(p);

von Karl heinz B. (kbucheg)


Lesenswert?

Francesco.

Wenn du mir nicht glaubst, dann glaub wenigstens dem
Mann der deine C-Library geschrieben hat.

> Nein, bis es auf ein richtiges NULL trifft.

Und wie bitte soll strcat das unterscheiden können?
Im String ist ein '\0' Zeichen drinnen. Und das
ist für strcat das Zeichen, dass hier der String
zuende ist.


Das hier, mein Freund:

> char buff[20]="";char*p;
> strcpy(buff,"eins\0zwei\0drei\0");
> for(p=buff;*p;p+=strlen(p)+1) puts(p);

funktioniert im übrigen nicht. Wenn du Glück hast
wird grade noch "eins" ausgegeben. Wenn du
Pech hast, dann produziert dir das Ganze einen
Speicherdump des halben Hauptspeichers.

Hast du das ausprobiert bevor du das gepostet hast?
Solltest du!

von ed. (Gast)


Lesenswert?

Hallo Karl Heinz,

danke für deine Ausführungen. So richtig funktioniert die ganze Sache 
aber leider immer noch nicht. Hab gerad keine Zeit mich mit dem Problem 
weiter zu beschäftigen. Habe noch einen anderen Lösungsansatz, welcher 
wunderbar funktioniert, aber nicht so elegant ist. Deswegen läuft die 
Sache insgesamt. Es interessiert mich aber trotzdem, warum ich mit 
diesem Weg solche Probleme habe, deswegen werde ich mich im Laufe der 
Woche nocheinmal melden.

Gruß ed.

von Karl H. (kbuchegg)


Lesenswert?

OK. Ist im Grunde ganz einfach.

Wenn dein ursprünglicher String in Buffer so ausgesehen hat

....User=abcd&\rPassword=xyz&\r.....

000000000011111 11111222222222 2333333
012345678901234 56789012345678 9012345

Jetzt suchst du nach Password zuerst:
Der Text "Password=" wird auch tatsächlich
gefunden. Er startet an Position 15. Daraus
leitest du ab, dass die Daten an Position 24
beginnen und durch die Suche nach "&\r" weist
du dass sie an Position 26 enden.

Soweit so gut. Aber jetzt kommts. Jetzt setzt
du nach den Daten einen '\0' rein

....User=abcd&\rPassword=xyz\0

000000000011111 1111122222222 2 2333333
012345678901234 5678901234567 8 9012345

Das Problem, das du jetzt erzeugt hast, besteht
darin, dass du alles was rechts vom '\0' steht
vom String abgetrennt hast. Wenn deine strlen()
von dem String vorher beispielsweise 35 war, dann
ist sie jetzt nur noch 26. Keine str... Funktion
schaut dir den Teil rechts vom '\0' nochmal an.

Jetzt suchst du weiter nach dem "User=" und findest
den auch. etc.

Jetzt drehen wir die Abfragen mal um.
Wieder zurück zur Ausgangsposition:

....User=abcd&\rPassword=xyz&\r.....

000000000011111 11111222222222 2333333
012345678901234 56789012345678 9012345

gesucht wird nach "User=". Wird auch gefunden an
Position 4. Restlicher Klimbin bringt raus, dass die
Nutzdaten an 9 anfangen und an 12 enden. Soweit so
gut. Aber dann wieder. Du ersetzt das Zeichen nach
den Daten durch einen '\0'.

Damit kriegst du

....User=abcd\0\rPassword=xyz&\r.....

00000000001111 1 11111222222222 2333333
01234567890123 4 56789012345678 9012345

Und jetzt hast du ein Problem: Der String ist wieder
verkürzt worden. Ab sofort geht der String für alle
str... Funktionen nur noch bis Position 12. An Position
13 steht der '\0' und der ist für alle str... Funktionen
das Kennzeichen dafür, dass hier der String zuende ist.

Ein strlen() würde hier zb. nur noch 12 zurückliefern.

Dadurch ist aber klar, dass bei der nächsten Suche, nämlich
nach "Password=" dieser String einfach nicht mehr gefunden
wird. Der String in dem gesucht wird ist simpel gesprochen
früher zuende, als der Suchstring da drin enthalten ist.
Für strstr ist der String an Position 12 bereits aus,
dein Suchmuster fängt aber erst bei 15 an.

Das ist das ganze Problem das du hast. Und mit der jetzigen
Vorgehensweise ist das auch nicht schön zu lösen.

von ed. (Gast)


Lesenswert?

Hallo Karl Heinz,

ich hätte vll. erwähnen sollen, das ich das mit dem Einfügen von '\0' 
schon rausgenommen habe. Ich habe aber irgendwo noch ein Problem mit der 
dynamischen Speicherallokierung. Im Buffer befindet sich ein 
HTML-Formular, die relevanten Daten in dieser Form 
"....User=abcd&Password=xyz\r\n". In der Funktion getKeyVal() werden 
meinen Daten jetzt schon richtig extrahiert, nach Rückgabe der Zeiger 
stimmen Sie dann wieder nicht (manchmal). Wenn ich Zeit hab setzte ich 
den Code nochmal rein.

Gruß und Danke ed.

von ed. (Gast)


Lesenswert?

Hallo Karl Heinz,

hab mich nochmal rangesetzt, jetzt scheint es zu laufen. Das 
Unterprogramm wird jetzt folgendermaßen aufgerufen, und die Daten 
ordentlich extrahiert.
1
  char* userpw = getFormValue("User", BUFFER);
2
  char* username = (char*)calloc(strlen(userpw)+1, sizeof(char));
3
  strcpy(username,userpw);
4
  
5
  userpw = getFormValue("Password", BUFFER);
6
  char* password = (char*)calloc(strlen(userpw)+1, sizeof(char));
7
  strcpy(password,userpw);
8
  .
9
  .// Überprüfung von username und password
10
  .
11
  free(username);
12
  free(password);
Das Unterprogramm sieht jetzt folgendermaßen aus:
1
char* getFormValue(char* Keyword,char* Key)
2
{
3
  char* newKeyword = (char*)calloc(strlen(Keyword) + 2, sizeof(char));
4
  strcpy(newKeyword, Keyword);
5
  strcat(newKeyword,"=");
6
7
  char* value = strstr(Key, newKeyword);
8
  value += strlen(newKeyword);
9
  int len = strcspn(value,"&\r");
10
  char* rvalue = (char*)calloc(len + 1, sizeof(char));
11
  strncpy(rvalue, value, len);
12
  value = rvalue;
13
  
14
  free(newKeyword);
15
  free(rvalue);
16
  return value;  
17
}
Bis jetzt läuft alles korrekt, und ich konnte noch kein Fehlverhalten 
feststellen. Falls du noch Anmerkungen hast, oder der Meinung bist, man 
könnte manche Sachen noch besser machen, bin ich für jede Anregung 
dankbar.

Gruß ed.

von Karl H. (kbuchegg)


Lesenswert?

> Bis jetzt läuft alles korrekt

zufällig.
Du kannst das so nicht machen:

  char* rvalue = (char*)calloc(len + 1, sizeof(char));
  strncpy(rvalue, value, len);
  value = rvalue;

  free(newKeyword);
  free(rvalue);
  return value;

Auch wenn du den Pointer, den du vom calloc kriegst,
beim return value nennst. Es ist immer noch derselbe
Pointer, den du vom calloc erhalten hast. Und den
Speicher auf den value (aka: rvalue) zeigt, hast du
vor dem return freigegeben.

Wenn ich mal deine Compiler-Täuschungsversuche rückgängig
mache, dann steht da:

  char* rvalue = (char*)calloc(len + 1, sizeof(char));
  strncpy(rvalue, value, len);

  free(newKeyword);
  free(rvalue);
  return rvalue;

Jetzt sieht mans besser, dass du da was illegales machst.

Dein Glück ist momentan nur, dass du mit dem Pointer
ausserhalb der Funktion sofort neuen Speicher allokierst
und umkopierst. Da braucht es schon sehr viel Pech,
wenn zwischen dem free() in der Funktion und dem strcpy()
ausserhalb der Funktion sich das Betriebssystem einklinkt
und den mitlerweile freigegebenen Speicher für andere
Zwecke benutzt, sodass beim strcpy was völlig falches
gelesen wird (oder das BS eine Speicherschutzverletzung
feststellt). Nur: 'sehr viel Pech' bedeutet nicht, dass es
nie passieren wird. Laut Murphy passiert dir das genau
dann, wenn du es zum ersten Mal deinem Kunden vorführst :-)

In C gibt es keine wirklich schöne Lösung um einen String
aus einer Funktion herauszubekommen. Du hast folgende
Möglichkeiten
* Ein char Array in die Funktion hineinübergeben, dass gross
  genug ist, um den String aufnehmen zu können. Zusätzlich
  zum Array sollte so eine Schnittstelle auch die Grosse dieses
  Arrays beinhalten, damit die Funktion ein Überlaufen des
  Arrays verhindern kann. Sehr oft   wird das mit einer zweiten
  Funktion kombiniert, die ausrechnet   wie gross denn dieses
  Array sein muss.

  Nachteil: Wenn der Aufrufer faul ist, dann gibt er einfach ein
  konstantes Array mit einer bestimmten Größe an und hofft, dass
  das immer reichen wird.

  In diese Kategorie fällt auch eine berühmte 'Functiona non grata':
  Was ist falsch an der C Standard-Funktion gets()?
  Antwort: gets() hat keine Möglichkeit sich gegen Bufferoverflow
  zu schützen. Immer fgets benutzen!

* Die Funktion allokiert genügend Speicher für den String und
  gibt einen pointer darauf zurück.

  Nachteil: Der Aufrufer darf nicht vergessen, auf diesen Pointer
  free() aufzurufen, wenn er den String nicht mehr braucht.

* Die Funktion verwendet einen funktionslokalen Buffer, den sie
  selbst verwaltet und gibt einen Pointer darauf zurück.

  Nachteil: Der Aufrufer kriegt Schwierigkeiten, wenn er mehrere
  Aufrufe hintereinander tätigt und nicht sofort nach Erhalt
  des Pointers eine Kopie der Daten anlegt. Für ihn sieht es
  dann so aus, als ob bereits erhaltene Daten, plötzlich
  den Wert ändern würden.


Und bevor du fragst: Es gibt keine 'beste Möglichkeit'. Alle
3 sind Krücken, mit denen man leben muss. Hier zeigt sich
eine der grossen Schwachstellen in C.

von ed. (Gast)


Lesenswert?

yadayada

Also rvalue zurückgeben und den Pointer (Speicherbereich) dann außerhalb 
der Funktion freigeben.

gruß ed.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.