URL's parsen



  • Hallo,

    Ich kam auf die Idee einen Parser für URL's zu basteln, wo mir aufgefallen ist, dass das nicht so einfach ist, wie es aussieht.

    Deshalb beschränke ich mit derzeit auf HTTP URL's.

    Ich habe einen kleinen Parser dazu geschrieben, die rein mittels std::string Operationen einer HTTP URL parst.

    Meine URL Klasse sieht wie folgt aus:

    class http_url{
    public:
        static std::optional<http_url> parse(const std::string& s);
    
        std::string scheme, host, user, password, path, query, fragment;
    };
    

    Die Parsing Funktion ist allerdings sehr tolerant, was die Eingabe betrifft. So werden illegale URL Zeichen trotzdem aktzeptiert, was ich auch weiter tolerieren will. Desweiteren hantiere ich mit std::string::find() herum, um Anfang und Ende der einzelnen URL-Teile zu finden und substringe dann jeweils die einzelnen Teile.

    Ist das ein guter Ansatz? Oder was würdet ihr empfehlen?

    Ich stelle mir vor die URL Klasse in einer eigenen Library einzupflegen. Deshalb frage ich mich, wann die Klasse gut genug gebacken ist, sie auch großflächig in anderen Projekten anzubieten.

    Mir fällt leider keine perfekte Vorgehensweise ein für den Parser. Gibt kleine Schlupflöcher, beispielsweise prüfe ich derzeit nicht, ob sich das erst gefundene @-Zeichen auch vor dem Pfadanfang befindet. Außerdem erlaube ich Zeichen, die in einer validen URL nicht vorkommen dürfen, was einen perfekten Parser wohl ausschließen dürfte. Was meint ihr?

    Grüße


  • Mod

    Deine Frage ist also, ob eine URL aus den Teilen "scheme, host, user, password, path, query, fragment" besteht, und ob man das mittels find parsen kann, ohne dass du uns konkreten Code zeigst?

    Zum ersten Teil: Jain, da fehlt noch ein Port, der ein Teil sein kann. Aber ich vermute mal, das hast du bloß in deiner Aufzählung in deiner Frage hier vergessen. Denn das ist zu offensichtlich, als dass du es im echten Code vergessen hättest. Die Teile hast du ja sicherlich aus irgendeiner Referenz (wo Ports drinstehen würden) und hast du dir nicht selber ausgedacht.

    Zum zweiten Teil: Ja, die Syntax von URLs ist sehr einfach und schreibt vor, dass die Trennzeichen, wenn sie Teil der oben genannten Komponenten sind, als Prozent-Sequenz angegeben werden müssen. Das heißt, eine naive Suche nach den vordefinierten Trennzeichen wird zum korrekten Ergebnis führen. Falls die URL gültig ist. Aber ob sie gültig ist oder nicht kann man ja dadurch prüfen, ob am Ende vom Parsen noch Zeichen übrig sind oder nicht.

    Ob du das auch alles richtig gemacht hast, kann man natürlich nicht sagen, anhand deiner Frage. Aber prinzipiell möglich ist, es, und auch gar nicht so schwer, als ob man da viel falsch machen könnte. Im Zweifelsfall erkennt man ein paar ungültige URLs fälschlicherweise als gültig (deswegen schreibst du sicherlich aus, dass du tolerant wärst bezüglich falscher Zeichen), aber der Schaden bei falschen Positivparses dürfte sich in Grenzen halten. Jedenfalls ist es schwer, eine gültige URL falsch zu parsen.

    Ansonsten empfehle ich:
    https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Generic_syntax
    Ist ein bisschen allgemeiner als das, was du gefragt hast, aber dein Spezialfall wird im Fließtext auch behandelt.



  • @SeppJ sagte in URL's parsen:

    Deine Frage ist also, ob eine URL aus den Teilen "scheme, host, user, password, path, query, fragment" besteht, und ob man das mittels find parsen kann, ohne dass du uns konkreten Code zeigst?

    Darfst du gerne fragen. Dann will ich mal meine aktuelle Parsing-Funktion posten. Scheint auch bis auf weiteres (für korrekte URL's) zu funktionieren.

    std::optional<http_url> http_url::parse(const std::string& s){
        /* PARSE SCHEME */
    
        http_url result;
        std::size_t scheme_end = s.find("://");
    
        if(scheme_end == s.npos)
            return std::nullopt;
    
        result.scheme = s.substr(0, scheme_end);
    
        std::size_t host_begin = scheme_end + 3;
    
        {
            std::string temp(result.scheme.size(), '\0');
            std::transform(result.scheme.begin(), result.scheme.end(), temp.begin(), &::toupper);
    
            if(temp != "HTTPS" && temp != "HTTP")
                return std::nullopt;
        }
    
        /* PARSE AUTHENTICATION */
    
        std::size_t auth_end = s.find('@', host_begin);
    
        if(auth_end != s.npos){
            std::string auth_string = s.substr(host_begin, auth_end - host_begin);
            std::size_t delim_pos = auth_string.find(':');
    
            if(delim_pos == auth_string.npos)
                return std::nullopt;
    
            result.user = auth_string.substr(0, delim_pos);
            result.password = auth_string.substr(++delim_pos);
    
            host_begin = ++auth_end;
        }
    
        /* PARSE HOST */
    
        std::size_t path_begin = s.find('/', host_begin);
    
        if(path_begin == s.npos){
            result.host = s.substr(host_begin);
            result.path = "/";
    
            return result;
        }
    
        result.host = s.substr(host_begin, path_begin - host_begin);
    
        /* PARSE PATH */
    
        std::size_t path_end = s.find_first_of("?#", path_begin);
    
        if(path_end == s.npos){
            result.path = s.substr(path_begin);
            return result;
        }
    
        result.path = s.substr(path_begin, path_end - path_begin);
    
        if(path_end == s.size() - 1)
            return result;
    
        /* PARSE QUERY */
    
        std::size_t query_begin = path_end;
        std::size_t fragment_begin = s.find('#', query_begin + 1);
    
        result.query = s.substr(query_begin, fragment_begin == s.npos ? s.npos : fragment_begin - query_begin);
    
        if(fragment_begin != s.npos)
            result.fragment = s.substr(fragment_begin);
    
        return result;
    }
    

    @SeppJ sagte in URL's parsen:

    Zum ersten Teil: Jain, da fehlt noch ein Port, der ein Teil sein kann. Aber ich vermute mal, das hast du bloß in deiner Aufzählung in deiner Frage hier vergessen.

    Stimmt. Das werde ich noch implementieren. Momentan wird der Port, wenn angegeben, einfach dem Host angefügt.

    @SeppJ sagte in URL's parsen:

    Zum zweiten Teil: Ja, die Syntax von URLs ist sehr einfach und schreibt vor, dass die Trennzeichen, wenn sie Teil der oben genannten Komponenten sind, als Prozent-Sequenz angegeben werden müssen. Das heißt, eine naive Suche nach den vordefinierten Trennzeichen wird zum korrekten Ergebnis führen. Falls die URL gültig ist. Aber ob sie gültig ist oder nicht kann man ja dadurch prüfen, ob am Ende vom Parsen noch Zeichen übrig sind oder nicht.

    Naja ich will ja auch per Definition illegale Zeichen erlauben lassen (Zeichen außer $-_.+!*'(),). Ich glaube dann ist kein Ende in Sicht.

    @SeppJ sagte in URL's parsen:

    Ob du das auch alles richtig gemacht hast, kann man natürlich nicht sagen, anhand deiner Frage. Aber prinzipiell möglich ist, es, und auch gar nicht so schwer, als ob man da viel falsch machen könnte. Im Zweifelsfall erkennt man ein paar ungültige URLs fälschlicherweise als gültig (deswegen schreibst du sicherlich aus, dass du tolerant wärst bezüglich falscher Zeichen), aber der Schaden bei falschen Positivparses dürfte sich in Grenzen halten. Jedenfalls ist es schwer, eine gültige URL falsch zu parsen.

    Natürlich gibts einige Fallstricke, meines Blicks nach für ungültige URL's. Ich habe die Klasse mit einer Reihe gültigen URL's getestet und habe bisher noch keinen Fehler gefunden. Natürlich würde ich auch gerne bei derart ungültigen URL's ein std::nullopt zurückgeben. Aber ob dies den Aufwand auch Wert ist?

    @SeppJ sagte in URL's parsen:

    Ansonsten empfehle ich:

    Ich könnte natürlich auch anhand des RFC's nur exakt die erlaubten Zeichen parsen, aber daran tu ich mich schon etwas schwieriger... Vielleicht schreibe ich eine weitere Klasse dazu.



  • This post is deleted!


  • @Luks-Nuke sagte in URL's parsen:

    Ich könnte natürlich auch anhand des RFC's nur exakt die erlaubten Zeichen parsen, aber daran tu ich mich schon etwas schwieriger...

    https://en.cppreference.com/w/cpp/algorithm/all_any_none_of


  • Mod

    Da ich es momentan nicht sehe: Übersetzt du auch prozent-codierte Zeichen zurück? Interessiert den Nutzer ja sicherlich schon, ob sein Passwort "12%3A3" oder "12:3" ist.



  • @SeppJ sagte in URL's parsen:

    Da ich es momentan nicht sehe: Übersetzt du auch prozent-codierte Zeichen zurück? Interessiert den Nutzer ja sicherlich schon, ob sein Passwort "12%3A3" oder "12:3" ist.

    Ja, ich hatte vor noch entsprechende encode() und decode() Funktionen für die einzelnen Teile bereitzustellen.



  • @SeppJ sagte in URL's parsen:

    Da ich es momentan nicht sehe: Übersetzt du auch prozent-codierte Zeichen zurück? Interessiert den Nutzer ja sicherlich schon, ob sein Passwort "12%3A3" oder "12:3" ist.

    Ist das noch "in"? Passwörter mitschicken?


  • Mod

    @Swordfish sagte in URL's parsen:

    Ist das noch "in"? Passwörter mitschicken?

    Natürlich nicht.



  • @Swordfish sagte in URL's parsen:

    @Luks-Nuke sagte in URL's parsen:

    Ich könnte natürlich auch anhand des RFC's nur exakt die erlaubten Zeichen parsen, aber daran tu ich mich schon etwas schwieriger...

    https://en.cppreference.com/w/cpp/algorithm/all_any_none_of

    Ich meinte die RFC's.



  • Spricht irgendwas dagegen wenn ich fürs Percent Encoding / Decoding die C-Funktionen std::sprintf() und std::sscanf() benutze?


  • Mod

    @Luks-Nuke sagte in URL's parsen:

    Spricht irgendwas dagegen wenn ich fürs Percent Encoding / Decoding die C-Funktionen std::sprintf() und std::sscanf() benutze?

    Und damit würdest du WAS tun? Nicht dass ich mir nicht vorstellen kann, dass man das damit irgendwie hinhacken könnte, aber alles was mir einfällt kommt mir sehr viel umständlicher vor als die näherliegende Möglichkeit die fertigen string-Funktionen zu nutzen. Selbst ohne diese Funktionen fände ich direktes Durchlaufen und Ersetzen immer noch wesentlich einfacher als ein sprintf-Hack, und Durchlaufen und Ersetzen ist schon relativ umständlich.



  • This post is deleted!

Log in to reply