Clever Informationen aus String einlesen



  • Hallo Forum,

    zur Zeit suche ich möglichst effektive Arten und Weise, aus einem gegebenen char-Array Informationen auszulesen. In besagtem Array stehen die RFC.822 Header Informationen zu einer Mail, aus welchen ich Dinge wie Absender, Datum etc. auslesen möchte. Derzeit sieht meine vorgehensweise wie folgt aus:

    char *from;
    char buffer[BUF];
    
    buffer = infos_einlesen();
    from = strstr(buffer, "From: ");
    from += strlen("From: ");
    from = strsep(from, "\r"); // bis zum Zeilenende lesen
    

    Diese Methode funktioniert bisher wie sie soll, scheint mir aber aufgrund der benötigten Variablenmenge etwas uneffektiv. Abgesehen von eher unschöner Syntax and whatnot.

    Lösungen wie sscanf scheinen bei diesem Problem von vornherein ausgeschlossen. Daher würde ich gerne wissen, wie eine optimiertere, einfache oder wie auch immer verbesserte Lösung aussehen könnte.

    Gruß!

    EDIT: ich meinte strsep anstatt strtok. Nun im Beispiel angepasst.


  • Mod

    Erst einmal fällt auf, dass du selber RFC822 falsch umsetzt. Ein "field" geht nicht bis zum Zeilenende und ein Zeilenende ist auch kein einfaches '\r'. Die genaue Definition ist:

    RFC822 schrieb:

    field       =  field-name ":" [ field-body ] CRLF
         
         field-name  =  1*<any CHAR, excluding CTLs, SPACE, and ":">
         
         field-body  =  field-body-contents
                        [CRLF LWSP-char field-body]
         
         field-body-contents =
                       <the ASCII characters making up the field-body, as
                        defined in the following sections, and consisting
                        of combinations of atom, quoted-string, and
                        specials tokens, or else consisting of texts>
    

    Zu einem gegebenen Feldnamen musst du also den nächsten Doppelpunkt (oder Ende des Headers) finden. Die Zeile mit dem nächsten Doppelpunkt zählt nicht mehr zum Feldinhalt. Prinzipiell kann ein Feld mehrmals vorkommen, typisches Beispiel die "Received"-Felder. Wir benötigen noch eine Definiton des Zeichensatzes:

    RFC-822, gekürzt schrieb:

    ; (  Octal, Decimal.)
         CHAR        =  <any ASCII character>        ; (  0-177,  0.-127.)
         CTL         =  <any ASCII control           ; (  0- 37,  0.- 31.)
                         character and DEL>          ; (    177,     127.)
         CR          =  <ASCII CR, carriage return>  ; (     15,      13.)
         LF          =  <ASCII LF, linefeed>         ; (     12,      10.)
         SPACE       =  <ASCII SP, space>            ; (     40,      32.)
         HTAB        =  <ASCII HT, horizontal-tab>   ; (     11,       9.)
         CRLF        =  CR LF
    
         LWSP-char   =  SPACE / HTAB                 ; semantics = SPACE
    

    Du siehst, die Regeln sind nicht ganz so einfach. Wenn du auch noch kontrollieren möchtest, ob die Regeln eingehalten wurden, dann wird es ganz schön kompliziert. Wesentlich komplizierter als deine jetzige Lösung. Da gibt es auch keinen Trick, manchmal muss man da eben einfach durch. Wie ich vorgehen würde (Pseudocode):

    Gegeben: kompletter Header
    Gegeben: Gesuchtes Feld "feld"
    
    Suche in Header nach "feld:"
    Falls gefunden:
      Suche nach nach nächstem ':'
      Falls gefunden:
        Suche nach vorhergehendem CRLF
        Falls gefunden und hinter "feld:":
          Alles zwischen "feld:" und CRLF ist Feldinhalt
          Fertig
        Andernfalls:
          Header ist ungültig.
      Andernfalls:
        Alles zwischen "feld:" und Ende ist Feldinhalt
        Fertig
    Andernfalls:
      Feld existiert nicht
      Fertig
    

    Kannst du ja mal umsetzen. Ist sicherlich nicht sooo schwierig, wie es auf den ersten Blick aussieht. Aber schön elegant wird es nicht.

    ⚠ Oder: Du suchst mal im Netz. Du bist schließlich nicht der erste, der so etwas machen möchte. Du wirst sehen, es gibt da schon fertige Bibliotheken dafür. Und nicht bloß für einfache Header in einfachen Emails, sondern gleich für den kompletten MIME-Standard. RFC-822 ist nämlich nicht mehr wirklich aktuell.



  • Vielen Dank für den Hinweis zum Protokollverständnis. Das ist mein erstes Mal, dass ich mich mit der konkreten Implementierung eines Protokolls auseinandersetze und da habe ich an solche Dinge erst einmal nicht im Geringsten gedacht. Ich schätze jedoch, dass es für mein derzeitiges Ausprobieren eventuell zu weit führt. Die Gründlichkeit sollte aber dennoch in Erinnerung gehalten werden.

    Mittlerweile merke ich, dass ich ohnehin großzügig mit den Header Informationen verfahren muss. Daher werde ich für's erste bei meiner nicht ganz Protokoll konsistenten Lösung bleiben. Sollten andere Lösungen (konkret bezogen auf die syntaktische Fragestellung) dennoch auftauchen, freue ich mich über jeden Hinweis!


  • Mod

    per schrieb:

    Sollten andere Lösungen (konkret bezogen auf die syntaktische Fragestellung) dennoch auftauchen, freue ich mich über jeden Hinweis!

    Dir ist klar, dass strsep (und üblicherweise auch strtok) den Ausgangsstring verändern? Falls das nicht gewünscht ist, nimm sscanf, um von deiner Startposition an bis zum nächsten '\r' (oder Ende) zu lesen. Eine volle Protokollimplementierung mit sscanf ist wohl wirklich unmöglich, aber wenn du bloß die Zeile bis zum Ende lesen möchtest, ist es ein sehr gutes Mittel.



  • Ja, das war mir bewusst. Da ich annahm, dass jeder Mailserver eine einheitliche Reihenfolge der einzelnen Felder bedient, wäre dies auch nicht weiter schlimm gewesen. Doch dem ist leider nicht so, weshalb ich am ehesten auf die sscanf-Methode (Art und Weise, that is) zurückgreifen würde. Doch leider bekomme ich bei folgendem Vorgang immer nur einen NULL Pointer zurückgeliefert.

    struct mail {
       char from[100]; 
    }; //Beispielstruct
    
    char header[BUF];
    char *from;
    struct mail *m = (struct mail *) malloc (sizeof(struct mail));
    
    lies_info_ein(header, BUF);
    if (from = strstr(header, "From:"))
       sscanf(from, "From: %s\r\n", m->from);
    

    Nun habe ich noch nicht sehr viel Routine im Umgang mit diesen Dingen und da werde ich sicher die ein oder andere Sache übersehen haben, aber im Grunde dürfte dem angesichts der Signatur von sscanf nichts entgegen stehen ... (?) Ansonsten bitte ich um Zurechtweisung!

    Gruß, Per



  • per schrieb:

    header = lies_info_ein();
    

    so bekommst du die daten nicht in dein array.
    eher so

    int lies_info_ein ( char header[], size_t header_size )
    {
    // hier maximal header_size bytes im char-array header speichern
    }
    


  • Da hätte ich präziser sein sollen und die angesprochene Stelle als Pseudocode kennzeichnen müssen. Ich werde dies korrigieren.



  • hast du geprüft ob in deinem array auch "From:" enthalten ist?



  • Fehlerprüfung wird gemacht, bzw. Abfrage auf Rückgabewert von strstr(). Entschuldigt die ungenügende Ausdrucksweise. Werde das Beispiel anpassen.



  • das kann immer noch krachen, wenn

    - malloc NULL liefert
    - per sscanf >= 100 zeichen konvertiert werden hat.



  • Nutze Funktionen für deine Aufgabenstellung, das erhöht die Übersichtlichkeit und das Debugging, beugt dem Codekopieren vor, usw.

    const char *lies(const char *s,const char *t,char *b)
    {
      char *p=strstr(s,t);
      if(p) sscanf(p,"%*s%999s",b),p=b;
      return p;
    }
    
    int main()
    {
      char *p,b[1000];
      if( p=lies("...From: bla ... To: fasel ...","From:",b) ) puts(p);
      if( p=lies("...From: bla ... To: fasel ...","To:",b) ) puts(p);
      if( p=lies("...From: bla ... To: fasel ...","Sender:",b) ) puts(p);
      return 0;
    }
    

Log in to reply