IsValidURL

Zur Vorstellung von Komponenten und Units für Lazarus
Benutzeravatar
Ally
Beiträge: 263
Registriert: Do 11. Jun 2009, 09:25
OS, Lazarus, FPC: Win und Lazarus Stable release
CPU-Target: x64

Re: IsValidURL

Beitrag von Ally »

Hallo,

@m.fuchs
Ich hatte ja eingangs erklärt was die Funktion machen soll. Sie validiert ob der übergebene String den Regeln einer Internetadresse entspricht. Sie überprüft nicht ob es die Adresse auch wirklich gibt.
Wenn es da noch Defizite gibt, die für den genannten oder ähnliche Anwendungsfälle von Bedeutung sind, dann bin ich für jeden Tipp dankbar.

@sstvmaster
Die Verwendung von RegExpr wurde ja von Warf schon vorgeschlagen. Das Ganze ist aber auch nicht ganz trivial und ob es effizienter ist als eine kleine Routine ist auch noch nicht geklärt.
Dein Vorschlag lässt z.B. noch so etwas zu: http://.www..blinde&=kuh..de/

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: IsValidURL

Beitrag von sstvmaster »

Hi Ally,

bin leider kein RegEx Profi.
Dein Vorschlag lässt z.B. noch so etwas zu: http://.www..blinde&=kuh..de/

Das kann sein, aber ich habe noch nie jemanden gesehen der das so eingegeben hätte.

Das Ganze ist aber auch nicht ganz trivial und ob es effizienter ist als eine kleine Routine ist auch noch nicht geklärt.

Ob effizient oder nicht, wenn du mal im WWW nach "Regex valid URL" suchst, dann wird zu 90% regex benutzt. Egal ob PHP, Perl, C, JavaScript.

LG Maik
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: IsValidURL

Beitrag von m.fuchs »

Ally hat geschrieben:Ich hatte ja eingangs erklärt was die Funktion machen soll. Sie validiert ob der übergebene String den Regeln einer Internetadresse entspricht.

Nö, macht sie nicht. Schon die Tatsache dass nur HTTP und HTTPS als Protokoll erlaubt ist zeigt, dass es nicht allgemeingültig ist.
Das ist auch nicht schlimm, solange es für deinen Anwendungsfall ausreichend ist. Aber eben auch nicht allgemeingültig für URL so dass ich die Funktion anders benennen würde.

Ally hat geschrieben:Sie überprüft nicht ob es die Adresse auch wirklich gibt.

Dies wäre auch nicht lösbar.

Ally hat geschrieben:Wenn es da noch Defizite gibt, die für den genannten oder ähnliche Anwendungsfälle von Bedeutung sind, dann bin ich für jeden Tipp dankbar.


Hab mal einen kleinen Test gemacht:


Wie gesagt: andere Protokolle müssten dazu kommen, IPv6 funktioniert nicht, Portnummern werden nicht erkannt...
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: IsValidURL

Beitrag von Timm Thaler »

af0815 hat geschrieben:Im Browser kann man ohne www arbeiten, das wird als default ergänzt. Also lazarusforum.de reicht schon.


Das mag schon sein, aber eine Funktion, die prüft ob eine Url valide ist, sollte als erstes prüfen ob das Protokoll vorhanden ist. Ein eventuelles Protokoll zu erraten und davor zu setzen ist nicht Aufgabe der Funktion. Zumal ein Browser, wenn ich mich recht erinnere, verschiedene Versionen durchprobiert: domain.com, www.domain.com, mit http, mit https...

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6198
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: IsValidURL

Beitrag von af0815 »

Timm Thaler hat geschrieben:...sollte als erstes prüfen ob das Protokoll vorhanden ist. Ein eventuelles Protokoll zu erraten und davor zu setzen ist nicht Aufgabe der Funktion. ..

Es ist kein Protokoll, sondern wie in der Wiki ausgeführt ein Schema. Das die Funktion nicht raten soll ist mir jetzt auch klar. Wenn man laufend nur einen Bruchteil eintippt, dann wird man wirklich blind.

Ich würde mal sagen, das zuerst auf ein gültiges Schema geprüft wird. Und dann anhand des Schemas dann die unterschiedlichen Teile abgearbeitet werden.

Weil http:, https:, ftp:, file:, news:, mailto:, .... müssen ja unterschiedlich behandelt werden. Sind aber alles mögliche Schemas in gültigen URLs. Weil der Begriff des URLs ist weitläufiger als man vermutet.

Dazu kommen natürlich noch die ? erlaubten Umlaute ? siehe https://www.checkdomain.de/blog/domains ... n-problem/ als was soll man die auf Gültigkeit prüfen ? Als IDN oder als ACE ?

Das nur als Beispiel wie mittlerweile alles kompliziert geworden ist. :-)
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: IsValidURL

Beitrag von Timm Thaler »

Ja aber mal ehrlich, wer gibt denn Urls ein? Das Stichwort wird bei Google gesucht und dann der passende Link angeklickt.;-)

Benutzeravatar
Ally
Beiträge: 263
Registriert: Do 11. Jun 2009, 09:25
OS, Lazarus, FPC: Win und Lazarus Stable release
CPU-Target: x64

Re: IsValidURL

Beitrag von Ally »

@m.fuchs,
genau genommen hast du natürlich recht mit deinem Einwand gegen den Namen.
Die Bezeichnung URL definiert ja alle Protokolle, wie z.B. ftp, http, https, mailto, news, nntp, telnet oder file.
Wenn ich das richtig sehe, gehören IP-Adressen aber nicht dazu.
Danke für den Tipp mit den Portadressen, kommt zwar selten vor, könnte man aber einbauen.
Die Namen IsValidWebAddress oder IsValidWebsite würden für meine Funktion vielleicht besser passen. (Vorschläge willkommen.)

@Alle
Darüber hinaus würde mich auch interessieren wie IsValidURL in PHP, Perl, C, JavaScript usw. funktioniert.
Wird da die komplette URL-Norm geprüft? und wie sieht da dann der entsprechende RegEx-Ausdruck aus?

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: IsValidURL

Beitrag von sstvmaster »

@Ally: Schau dir das hier mal an, da sieht man das es nicht so einfach ist, mal eben ein regex zu machen.

https://mathiasbynens.be/demo/url-regex
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

Benutzeravatar
Ally
Beiträge: 263
Registriert: Do 11. Jun 2009, 09:25
OS, Lazarus, FPC: Win und Lazarus Stable release
CPU-Target: x64

Re: IsValidURL

Beitrag von Ally »

@sstvmaster,
die Seite zeigt eindrücklich was ich meine.
Und hier werden wohl meist auch nur HTTP und HTTPS geprüft.
Im Vergleich dazu, schneidet mein "Dreizeiler" erst mal gar nicht so schlecht ab. :)

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: IsValidURL

Beitrag von m.fuchs »

Ally hat geschrieben:Wenn ich das richtig sehe, gehören IP-Adressen aber nicht dazu.

Warum meinst du das? Also für reine IP-Adressen stimmt das natürlich, aber IP-Adressen an Stelle eines Hostnamens als Teil der URL ist schon zulässig.

BTW: Vielleicht kannst du dir hier noch Anregungen holen:
https://sourceforge.net/p/synalist/code ... .pas#l1303
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Benutzeravatar
Ally
Beiträge: 263
Registriert: Do 11. Jun 2009, 09:25
OS, Lazarus, FPC: Win und Lazarus Stable release
CPU-Target: x64

Re: IsValidURL

Beitrag von Ally »

So, ich habe der Unit einen passenderen Namen gegeben und die Überprüfung auf Portnummern eingebaut.
Den Test auf IPv6 habe ich, auf Grund fehlender Relevanz, noch nicht realisiert. (Ich habe keinen Text oder Link gefunden, in dem es zum Einsatz kommt.)
Einen kleinen Speedtest habe ich auch mal gemacht, 100.000 Aufrufe dauern 750 Millisekunden. (i5-Desktop-PC)
Mit RegEx wird das Programm 24KB größer und die Ausführung dauert je nach Länge der URL 1,5 bis 2x so lange, was in der Praxis wohl auch kein Problem darstellt.
Allerdings habe ich auf die Schnelle, keinen RegEx-Ausdruck gefunden der eine befriedigendes Ergebnis geliefert hätte. Und je länger die Ausdrücke werden um so länger wird auch die Laufzeit.

Code: Alles auswählen

unit rhsIsValidWebsiteURL;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, StrUtils;
 
function IsValidWebsiteURL(const sURL: String): Boolean;
 
implementation
 
function IsValidWebsiteURL(const sURL: String): Boolean;
var
  i: Integer;
  x: Integer;
  PartList: TStringArray;
  sAdress: String = '';
  sProtocol: String = '';
  sForbidden: String = ' :?#[]@!$&()*+,;=.' + #39;
const
  ProtocolList: array[0..4] of String = ('https://www.', 'http://www.', 'https://', 'http://', 'www.');
begin
  Result := False;
 
  // sURL darf keine Zeichen < #33 enthalten, oder mit einem reservierten Zeichen enden.
  if (PosSet([#0..#32], sURL) > 0) or (Pos(RightStr(sURL, 1), sForbidden) > 0) then
    Exit(False);
 
  // sURL muss mit einem der definierten Protokolle beginnen.
  for i := 0 to 4 do
  begin
    if Pos(ProtocolList[i], Lowercase(sURL)) = 1 then
    begin
      sProtocol := ProtocolList[i];
      Break;
    end;
  end;
  if sProtocol.IsEmpty then
    Exit(False);
 
  // Die Adresse darf incl. Protokoll maximal 255 Zeichen lang sein.
  sAdress := sURL.Substring(sProtocol.Length, 255 - sProtocol.Length);
 
  // Nur die Zeichen vor dem ersten Pfadtrenner gehören zur Adresse.
  i := sAdress.IndexOf('/');
  if i > -1 then
    sAdress := LeftStr(sAdress, i);
 
  // Auf Portnummer prüfen (http://www.domain.de:8080)
  i := sAdress.LastIndexOf(':');
  if i > -1 then
  begin
    if not TryStrToInt(RightStr(sAdress, sAdress.Length - i - 1), x) then
      Exit(False);
    sAdress := LeftStr(sAdress, i);
  end;
 
  // Adresse in Subdomain(s), Second-Level-Domain und Top-Level-Domain aufteilen.
  PartList := sAdress.Split('.');
 
  // Die Adresse muss aus mindestens zwei Teilen bestehen (Second-Level-Domain und Top-Level-Domain).
  if High(PartList) < 1 then
    Exit(False);
 
  // Die Adressteile dürfen nicht leer sein oder reservierte Zeichen enthalten.
  for i := 0 to High(PartList) do
    if (PartList[i].IsEmpty) or (PosSet(sForbidden, PartList[i]) > 0) then
      Exit(False);
 
  Result := True;
end;
 
end.
Zuletzt geändert von Ally am Sa 17. Aug 2019, 16:46, insgesamt 1-mal geändert.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: IsValidURL

Beitrag von Warf »

aber IP-Adressen an Stelle eines Hostnamens als Teil der URL ist schon zulässig.

Die Frage ist ob man das wirklich braucht. Effektiv laufen auf einem Server mit einer IP mehrere Webserver die aufgrund der Domain unterscheiden welcher Webserver angesprochen wird. Im umkehrschluss bedeutet das, auf produktivservern solltest du über die IP nichts erreichen (da der die IP keinem server zuordnen sollte). Klar ist es möglich, aber dafür n extra check einzubauen für was was effektiv nicht verwendet wird ist zwar möglich, mMn. aber nicht sonderlich sinnig

Wenn man ne ganz allgemeine funktion haben möchte, muss diese funktion dem RFC 3986 standard entsprechen. Das willst du dann aber auf jeden fall als regex machen, denn das wird dann so kompliziert das regex auf jeden fall effizienter sein sollte. Gehen wir mal auf Allys neuen code ein:

Code: Alles auswählen

  if (PosSet(' ' + #10 + #13, sURL) > 0) or (Pos(RightStr(sURL, 1), sForbidden) > 0) then
    Exit(False);

2x Pos => Im worst case 2 mal über den ganzen string laufen. (PS: du hast vergessen auf die anderen blank chars wie tab zu testen, afaik sind alle zeichen deren ascii wert <=32 ist verboten)

Code: Alles auswählen

  for i := 0 to 4 do
  begin
    if Pos(ProtocolList[i], Lowercase(sURL)) = 1 then
    begin
      sProtocol := ProtocolList[i];
      Break;
    end;
  end;

Lowercase + Pos ist nochmal 2 mal drüber laufen, *4 also 8 mal (PS: Du solltest die maxvalue der for schleife eventuell nicht hardcoden)
Sind wir schon bei 10 durchläufen

Code: Alles auswählen

sAdress := sURL.Substring(sProtocol.Length, 255 - sProtocol.Length);

Substring läuft effektiv auch einmal den ganzen string entlang (PS: wenn der string länger als 255 ist return direkt false)
11

Code: Alles auswählen

i := sAdress.IndexOf('/')

Noch ein durchlauf, 12

Code: Alles auswählen

i := sAdress.LastIndexOf(':');

13

Code: Alles auswählen

TryStrToInt(RightStr(sAdress, sAdress.Length - i - 1), x)

StrToInt + Rightstr: 15

Code: Alles auswählen

sAdress := LeftStr(sAdress, i);

16

Code: Alles auswählen

PartList := sAdress.Split('.');

17

Code: Alles auswählen

PosSet(sForbidden, PartList[i]) > 0

18

Du hast zwar schon durch die schlauere posset funktion einiges an durchläufen eingespart, du läufst aber immernoch 18 mal über den String.

Falls jemand mal eine RFC 3987 Compliant regex sehen möchte schau mal hier:

Code: Alles auswählen

/^[a-z](?:[-a-z0-9\+\.])*:(?:\/\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:])*@)?(?:\[(?:(?:(?:[0-9a-f]{1,4}:){6}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|::(?:[0-9a-f]{1,4}:){5}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|v[0-9a-f]+\.[-a-z0-9\._~!\$&'\(\)\*\+,;=:]+)\]|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}|(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=])*)(?::[0-9]*)?(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))*)*|\/(?:(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))*)*)?|(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))*)*|(?!(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@])))(?:\?(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@])|[\x{E000}-\x{F8FF}\x{F0000}-\x{FFFFD}\x{100000}-\x{10FFFD}\/\?])*)?(?:\#(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@])|[\/\?])*)?$/i

Sieht sehr kompliziert aus, kann man aber einfach copy und pasten wenn man eine wirklich allgemeingültige funktion braucht. Aber, ich sehe nicht wozu man eine absolut allgemeingültige funktion braucht. Für die meisten sachen würde schon die funktion aus dem ersten Post vollkommen ausreichend sein. Und wenn man versuchen würde das als nicht regex nachzuprogrammieren, mit den standardfunktionen wie pos, indexof, etc. dann hat man am ende locker 5000 zeilen code

PS:
Worüber wir noch gar nicht geredet haben sind Unicode URLs. Seit 2010 oder so dürfen URI's (und damit auch URL's) unocde charaktere enthalten. Von simplen sachen wie http://flüge.de bis hin zu http://رسيل.السعودية ist alles erlaubt. Das müsste deine funktion natürlich dann auch abdecken. Dazu kommen dann die ganzen tollen fremdsprachefeatures wie in der domain oben, da das arabisch ist, ist aus sicht eines dummen, auf englisch ausgelegten computers, falsch rum geschrieben, plötzlich ist die endung also vor dem domainnamen (Auch wenn die Unicode implementation der Programmiersprache sich eigentlich darum kümmern sollte weiß ich nicht wie das in Lazarus, vor allem den standardfunktionen wie pos oder indexof geht)
PPS: Die urls die ich im PS gepostet hab sollen keine werbung oder sowas sein, um ehrlich zu sein weiß ich nicht mal was das für ne arabische seite ist, ich kann kein arabisch, war das erste ergebnis als ich nach nem beispiel gesucht hab

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: IsValidURL

Beitrag von Timm Thaler »

Doch, schon. Wenn man auf so einen dämlichen Speedport angewiesen ist, der regelmäßig die vergebenen Hostnamen bei DynDNS vergißt ist die IP dann die Rettung um den Raspi noch zu erreichen. Oder wenn man selbst auf den Speedport muss, kann ein 192.168.2.1 auch sicherer gehen als ein speedport.ip. Vor allem wenn der dämliche Browser dann nicht zwischen Hostname und Googlesuche unterscheiden kann: Wenn ich einfach nur hausnetz eingeben, öffnet der Browser nicht die Webseite auf dem Raspi im Hausnetz, sondern - sucht nach "hausnetz" bei Google... :(

Benutzeravatar
Ally
Beiträge: 263
Registriert: Do 11. Jun 2009, 09:25
OS, Lazarus, FPC: Win und Lazarus Stable release
CPU-Target: x64

Re: IsValidURL

Beitrag von Ally »

@Warf,
Danke für den Hinweis mit den Zeichen unterhalb #33. Habe ich gleich geändert. (siehe oben im Quelltext)

Was die Durchläufe anbelangt, habe ich bei meinen Speedtests gültige URL's mit Portnummer benutzt. Somit war, was meine Routine anbelangt, der Worst Case erfüllt. :)
Praktisch alle Programmteile werden durchlaufe, langsamer geht es nicht.
Für den RegEx-Test habe ich das Beispiel von sstvmaster benutzt. Und zwar einmal mit dem Ausdruck von sstvmaster und einmal mit dem Ausdruck von dir. (Den, den du zuerst gepostet hast)
Dein Ausdruck ist länger brachte auch bessere, aber immer noch unbefriedigende, Ergebnisse. Schlussfolgerung: Je komplexer der Ausdruck um so mehr Zeit nimmt er in Anspruch?
Das hätte ich jetzt gerne mit deinem "Monster-Ausdruck" getestet, leider bringt der eine Fehlermeldung.

@Timm Thaler,
ja ok, aber für diesen Fall musst du die URL ja genau kennen, es muss also eine gültige URL sein, die du nicht auf Validität überprüfen musst.

PS:
Unicode URLs, wie http://flüge.de und http://رسيل.السعودية, funktionieren mit meiner Funktion auch.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: IsValidURL

Beitrag von Warf »

Für den RegEx-Test habe ich das Beispiel von sstvmaster benutzt. Und zwar einmal mit dem Ausdruck von sstvmaster und einmal mit dem Ausdruck von dir. (Den, den du zuerst gepostet hast)
Dein Ausdruck ist länger brachte auch bessere, aber immer noch unbefriedigende, Ergebnisse. Schlussfolgerung: Je komplexer der Ausdruck um so mehr Zeit nimmt er in Anspruch?
Das hätte ich jetzt gerne mit deinem "Monster-Ausdruck" getestet, leider bringt der eine Fehlermeldung.


Der ausdruck von mir (der lange) fängt mit / an und hört mit /i auf. Das ist beides nicht teil des regex, sondern so zu sagen stellt die ramenbedingungen fest (also das i am ende bedeutet das es caseinsensitiv matchen soll, sonst wär der ausdruck doppelt so lang). Du musst also praktisch für den test alles zwischen den / als regex nehmen und der regexklasse sagen das es canseinsensitiv matchen soll.

Das größere regexausdrücke länger dauern kann man nicht im allgemeinen sagen. Regex ist grundsätzlich automaten theorie, und ohne zu tief eindringen zu wollen ist die idee die folgende, Ein automat kann bestimmte worte Matchen. z.B. a.* beschreibt einen automaten der jedes wort matcht das mit a anfängt. Ein regex beschreibt also einen sehr großen automaten (eigentlich sogar viele kleine, die dann zu einem großen zusammengesetzt werden). Für eine sehr einfache Regex implementierung wird dieser automat dann ausgeführtm, und da gilt tatsächlich je größer der ausdruck desto langsamer, da einfach der automat der im Speicher liegt riesig ist.
Eine gute regex implementierung optimiert diesen automaten erstmal. Es gibt ne ganze reihe an optimierungen die man fahren kann, z.B. (http|https) (also http oder https) kann optimiert werden zu http(s)? (http gefolgt von einem s oder auch nicht).
Das heißt du kannst nen riesigen ausdruck haben der nach dem Optimieren einfach kleiner ist als ein anderer ausdruck der normal sehr klein ist, aber nicht reduzierbar ist.

Das wichtige bei regex ist aber das die Laufzeit nicht groß steigt wenn der String länger wird. Als beispiel, du rufst LastIndexOf, indexof und Pos auf. Das sind jeweils einmal für jede funktion über den string laufen. Du kannst aber theoretisch alle positionsinformationen sammeln indem du einmal über den string läufst und alle infos auf einmal rausziehst. Und genau das macht regex für dich. Du gibst den ausdruck an, und dann ist es job des Computers das in so wenig durchläufen wie möglich zu machen. Und es lässt sich formal beweisen das gut optimiertes Regex die optimale durlaufzahl findet.

D.h. Während du für sehr kleine regex das ganze per hand noch optimal bauen kannst (und somit der regex overhead wegfällt), würde für einen ausdruck wie den RFC kompatiblen den ich oben geschrieben hab, du dich dumm und dämlich programmieren, um effektiv kein besseres ergebnis zu erziehlen, außer eventuell auf sehr kleinen strings (auf denen der overhead von regex relevant ist).

Daher auch mein kommentar am anfang des threads das wenn du z.B. den ganzen inhalt einer nicht trivialen datei überprüfen willst (also u.a. sehr lange strings, bei denen der regex overhead vernachlässigbar ist, bzw. du nicht garantieren kannst das die strings kurz sind) ist regex auf jeden fall das mittel der wahl. Für kleine strings lässt sich darüber streiten. Ich gehe mal aus, grade mit der zeit die du brauchst um aus dem regex überhaupt den automaten zu bauen, ist schon mehr als du für eine url mit weniger als 10 zeichen zum parsen insgesammt brauchen würdest.

Alles eine frage des Usecases, das richtige werkzeug für den richtigen job. Für das was du ursprünglich gemacht hast reicht deine funktion vollends aus

PS: du musst auch unterscheiden zwischen der zeit die es braucht um den Automaten zu kompilieren und dem matching. Wenn die Implementation viele Optimierungen fährt kann es sein das das bauen des automaten extrem lange dauert, aber wenn das einmal gemacht ist kannst du danach beliebig viele Strings überprüfen

Antworten