Wyrażenie regularne pasujące do nazwy hosta DNS lub adresu IP?


369

Czy ktoś ma pod ręką wyrażenie regularne, które pasuje do dowolnej legalnej nazwy hosta DNS lub adresu IP?

Łatwo jest napisać taki, który działa w 95% przypadków, ale mam nadzieję, że dostanę coś, co jest dobrze przetestowane, aby dokładnie pasowało do najnowszych specyfikacji RFC dla nazw hostów DNS.

Odpowiedzi:


535

Możesz użyć następujących wyrażeń regularnych osobno lub łącząc je we wspólne wyrażenie LUB.

ValidIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$";

ValidHostnameRegex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$";

ValidIpAddressRegex pasuje do prawidłowych adresów IP i ValidHostnameRegex prawidłowych nazw hostów. W zależności od używanego języka \ może być konieczne użycie znaku ucieczki \.


ValidHostnameRegex jest ważny zgodnie z RFC 1123 . Pierwotnie RFC 952 określał, że segmenty nazwy hosta nie mogą zaczynać się cyfrą.

http://en.wikipedia.org/wiki/Hostname

Oryginalna specyfikacja nazw hostów w RFC 952 wymagała , aby etykiety nie zaczynały się cyfrą lub łącznikiem i nie mogły kończyć się łącznikiem. Jednak kolejna specyfikacja ( RFC 1123 ) zezwalała, aby etykiety nazw hostów zaczynały się od cyfr.

Valid952HostnameRegex = "^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$";

3
Tutaj: stackoverflow.com/questions/4645126/… - Wyjaśniam, że nazwy rozpoczynające się cyfrą są również uważane za prawidłowe. Również tylko jedna kropka jest wątpliwym problemem. Byłoby wspaniale mieć więcej opinii na ten temat.
BreakPhreak,

16
Możesz dodać IPv6. OP nie określił, jaki typ adresu. (Nawiasem mówiąc, można go znaleźć tutaj )
new123456

32
Zanim ludzie na oślep użyją tego w swoim kodzie, zauważ, że nie jest to całkowicie dokładne. Ignoruje RFC2181: „Sam DNS nakłada tylko jedno ograniczenie na poszczególne etykiety, które można wykorzystać do identyfikacji rekordów zasobów. To jedno ograniczenie dotyczy długości etykiety i pełnej nazwy. Długość dowolnej etykiety jest ograniczona do 1 i 63 oktety. Pełna nazwa domeny jest ograniczona do 255 oktetów (łącznie z separatorami). ”
rubel

7
@UserControl: Nazwy hostów niełacińskich (z kodami kreskowymi) należy najpierw przekonwertować na formularz ASCII ( éxämplè.com= xn--xmpl-loa1ab.com), a następnie zweryfikować.
Alix Axel

6
Twoje wyrażenie nazwy hosta jest zgodne z pewnymi niepoprawnymi wartościami: próbowałem 123.456.789.0i jest to poprawna nazwa hosta.
lbarreira

62

Wyrażenie regularne nazwy smink nie uwzględnia ograniczenia długości poszczególnych etykiet w nazwie hosta. Każda etykieta w prawidłowej nazwie hosta może mieć nie więcej niż 63 oktety.

ValidHostnameRegex = "^ ([a-zA-Z0-9] | [a-zA-Z0-9] [a-zA-Z0-9 \ -] {0,61} [a-zA-Z0-9]) \
(\. ([a-zA-Z0-9] | [a-zA-Z0-9] [a-zA-Z0-9 \ -] {0,61} [a-zA-Z0-9])) * $ ”

Zauważ, że ukośnik odwrotny na końcu pierwszego wiersza (powyżej) to składnia powłoki uniksowej do dzielenia długiej linii. To nie jest część samego wyrażenia regularnego.

Oto tylko wyrażenie regularne w jednym wierszu:

^ ([a-zA-Z0-9] | [a-zA-Z0-9] [a-zA-Z0-9 \ -] {0,61} [a-zA-Z0-9]) (\. ([a-zA-Z0-9] | [a-zA-Z0-9] [a-zA-Z0-9 \ -] {0,61} [a-zA-Z0-9])) * $

Powinieneś również sprawdzić to osobno czy całkowita długość nazwy hosta nie może przekraczać 255 znaków . Aby uzyskać więcej informacji, zapoznaj się z RFC-952 i RFC-1123.


6
Doskonały wzór hosta. Prawdopodobnie zależy to od implementacji wyrażenia regularnego w danym języku, ale dla JS można go nieco dostosować, aby był krótszy, nie tracąc niczego:/^[a-z\d]([a-z\d\-]{0,61}[a-z\d])?(\.[a-z\d]([a-z\d\-]{0,61}[a-z\d])?)*$/i
Semicolon

31

Aby dopasować prawidłowy adres IP, użyj następującego wyrażenia regularnego:

(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}

zamiast:

([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\.([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])){3}

Wyjaśnienie

Wiele silników wyrażeń regularnych odpowiada pierwszej możliwości w ORsekwencji. Na przykład wypróbuj następujące wyrażenie regularne:

10.48.0.200

Test

Sprawdź różnicę między dobrem a złem


5
Nie zapominaj, że początek ^ i koniec $ lub coś w rodzaju 0.0.0.999 lub 999.0.0.0 również będzie pasować. ;)
Andre

1
tak, aby zatwierdzić ciąg początkowy ^ i końcowy $ są wymagane, ale jeśli szukasz adresu IP w tekście, nie używaj go.
Alban

Niezamierzona „chciwość”, którą identyfikujesz, dotyczy również innych rozwiązań dotyczących nazw hostów. Warto dodać to do swojej odpowiedzi, ponieważ inni nie będą pasować do pełnej nazwy hosta. np. ([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*kontra([a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]|[a-zA-Z0-9])(\.([a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])|[a-zA-Z0-9]))*
ergohack,

EDYCJA: powyżej, użyj +na końcu zamiast *zobaczyć błąd.
ergohack,

5

Wydaje mi się, że nie mogę edytować najwyższego postu, więc dodam tutaj swoją odpowiedź.

Dla nazwy hosta - łatwa odpowiedź, na przykład egrep tutaj - http: //www.linuxinsight.com/how_to_grep_for_ip_addresses_using_the_gnu_egrep_utility.html

egrep '([[:digit:]]{1,3}\.){3}[[:digit:]]{1,3}'

Chociaż przypadek nie uwzględnia wartości takich jak 0 w pierwszym oktecie i wartości większych niż 254 (adresy IP) lub 255 (maska ​​sieci). Może pomoże dodatkowe oświadczenie if.

Co do legalnej nazwy hosta dns, pod warunkiem, że sprawdzasz tylko nazwy hostów internetowych (a nie intranet), napisałem następujący fragment, mieszankę powłoki / php, ale powinien on być stosowany jako każde wyrażenie regularne.

najpierw przejdź na stronę ietf, pobierz i przeanalizuj listę nazw domen poziomu 1:

tld=$(curl -s http://data.iana.org/TLD/tlds-alpha-by-domain.txt |  sed 1d  | cut -f1 -d'-' | tr '\n' '|' | sed 's/\(.*\)./\1/')
echo "($tld)"

To powinno dać ci ładny kod, który sprawdza legalność najlepszych nazw domen, takich jak .com .org lub .ca

Następnie dodaj pierwszą część wyrażenia zgodnie z wytycznymi tutaj - http: //www.domainit.com/support/faq.mhtml?category=Domain_FAQ&question=9 (dowolna kombinacja alfanumeryczna i symbol „-”, myślnik nie powinien być początek lub koniec oktetu.

(([a-z0-9]+|([a-z0-9]+[-]+[a-z0-9]+))[.])+

Następnie poskładaj wszystko razem (przykład PHP preg_match):

$pattern = '/^(([a-z0-9]+|([a-z0-9]+[-]+[a-z0-9]+))[.])+(AC|AD|AE|AERO|AF|AG|AI|AL|AM|AN|AO|AQ|AR|ARPA|AS|ASIA|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BIZ|BJ|BM|BN|BO|BR|BS|BT|BV|BW|BY|BZ|CA|CAT|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|COM|COOP|CR|CU|CV|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EDU|EE|EG|ER|ES|ET|EU|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GOV|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|INFO|INT|IO|IQ|IR|IS|IT|JE|JM|JO|JOBS|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MG|MH|MIL|MK|ML|MM|MN|MO|MOBI|MP|MQ|MR|MS|MT|MU|MUSEUM|MV|MW|MX|MY|MZ|NA|NAME|NC|NE|NET|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|ORG|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PRO|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|ST|SU|SV|SY|SZ|TC|TD|TEL|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TP|TR|TRAVEL|TT|TV|TW|TZ|UA|UG|UK|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|XN|XN|XN|XN|XN|XN|XN|XN|XN|XN|XN|YE|YT|YU|ZA|ZM|ZW)[.]?$/i';

    if (preg_match, $pattern, $matching_string){
    ... do stuff
    }

Możesz także dodać instrukcję if, aby sprawdzić, czy sprawdzany ciąg jest krótszy niż 256 znaków - http://www.ops.ietf.org/lists/namedroppers/namedroppers.2003/msg00964.html


1
-1, ponieważ odpowiada fałszywym adresom IP, takim jak „999.999.999.999”.
bdesham

1
„Chociaż sprawa nie uwzględnia wartości takich jak 0 w pierwszym oktecie i wartości większych niż 254 (adresy IP) lub 255 (maska ​​sieci).”
Alex Volkov

Widziałem, że kwalifikujesz się do odpowiedzi, tak. Odrzuciłem głos, ponieważ ta część twojej odpowiedzi wciąż nie jest przydatna.
bdesham,

3

Warto zauważyć, że istnieją biblioteki dla większości języków, które robią to za Ciebie, często wbudowane w bibliotekę standardową. Te biblioteki prawdopodobnie będą aktualizowane znacznie częściej niż kod, który skopiowałeś odpowiedź przepełnienia stosu cztery lata temu i zapomniałeś. I oczywiście zazwyczaj parsują adres w jakiejś użytecznej formie, zamiast po prostu dać ci dopasowanie do grupy.

Na przykład wykrywanie i analizowanie IPv4 w (POSIX) C:

#include <arpa/inet.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
  for (int i=1; i!=argc; ++i) {
    struct in_addr addr = {0};
    printf("%s: ", argv[i]);
    if (inet_pton(AF_INET, argv[i], &addr) != 1)
      printf("invalid\n");
    else
      printf("%u\n", addr.s_addr);
  }
  return 0;
}

Oczywiście takie funkcje nie będą działać, jeśli próbujesz np. Znaleźć wszystkie prawidłowe adresy w wiadomości czatu - ale nawet tam łatwiej jest użyć prostego, ale nadgorliwego wyrażenia regularnego, aby znaleźć potencjalne dopasowania, a następnie użyć biblioteka do ich parsowania.

Na przykład w Pythonie:

>>> import ipaddress
>>> import re
>>> msg = "My address is 192.168.0.42; 192.168.0.420 is not an address"
>>> for maybeip in re.findall(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', msg):
...     try:
...         print(ipaddress.ip_address(maybeip))
...     except ValueError:
...         pass

2
def isValidHostname(hostname):

    if len(hostname) > 255:
        return False
    if hostname[-1:] == ".":
        hostname = hostname[:-1]   # strip exactly one dot from the right,
                                   #  if present
    allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
    return all(allowed.match(x) for x in hostname.split("."))

Czy możesz wyjaśnić to wyrażenie regularne? Dokładnie, co oznaczają (?! -), (? <! -)?
Scit

1
@ Scit, upewnij się, że nie zaczyna się ani nie kończy na znaku „-”, jeśli silnik wyrażeń regularnych pozwala na ich użycie. Na przykład z Python lub Perl .
YLearn

1

Myślę, że to najlepszy regex sprawdzania poprawności IP. proszę sprawdź to raz !!!

^(([01]?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))\.){3}([01]?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))$

1
"^((\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])\.){3}(\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])$"

1

Działa to dla prawidłowych adresów IP:

regex = '^([0-9]|[1-9][0-9]|[1][0-9][0-9]|[2][0-5][0-5])[.]([0-9]|[1-9][0-9]|[1][0-9][0-9]|[2][0-5][0-5])[.]([0-9]|[1-9][0-9]|[1][0-9][0-9]|[2][0-5][0-5])[.]([0-9]|[1-9][0-9]|[1][0-9][0-9]|[2][0-5][0-5])$'

1
/^(?:[a-zA-Z0-9]+|[a-zA-Z0-9][-a-zA-Z0-9]+[a-zA-Z0-9])(?:\.[a-zA-Z0-9]+|[a-zA-Z0-9][-a-zA-Z0-9]+[a-zA-Z0-9])?$/

0

Oto wyrażenie, którego użyłem w Ant, aby uzyskać adres IP hosta proxy lub nazwę hosta z ANT_OPTS. Służyło to do uzyskania adresu IP serwera proxy, dzięki czemu mogłem uruchomić test Ant „nieosiągalny” przed skonfigurowaniem serwera proxy dla rozwidlonej maszyny JVM.

^.*-Dhttp\.proxyHost=(\w{1,}\.\w{1,}\.\w{1,}\.*\w{0,})\s.*$

To \wprawda, nie przechwytuje adresu IP, tylko nazwę hosta w określonych sytuacjach.
Yaron

0

Odkryłem, że działa całkiem dobrze w przypadku adresów IP. Sprawdza poprawność jak najwyższa odpowiedź, ale upewnia się również, że ip jest izolowane, więc żaden tekst ani więcej liczb / miejsc po przecinku nie jest przed ani przed ip.

(? <! \ S) (?: (?: \ D | [1-9] \ d | 1 \ d \ d | 2 [0-4] \ d | 25 [0-5]) \ b |. \ b) {7} (?! \ S)


Próbowałem dużo, ale nie mogłem zrozumieć 2 rzeczy tutaj. 1. \ b określa granicę słów Dlaczego używamy \ b? która jest granica? i 2. Dlaczego działa tylko dla {7} Z tego co zrozumiałem, myślę, że powinien to być {4}, ale nie działa. Opcjonalnie możesz powiedzieć, dlaczego używasz bloków nie przechwytujących.
Srichakradhar,


0

Spróbuj tego:

((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

to działa w moim przypadku.


0

Jeśli chodzi o adresy IP, wydaje się, że toczy się debata na temat tego, czy dołączyć wiodące zera. Kiedyś była to powszechna praktyka i jest ogólnie akceptowana, dlatego argumentowałbym, że należy je oznaczyć jako ważne bez względu na obecne preferencje. Istnieje również pewna dwuznaczność co do tego, czy tekst przed i po ciągu powinien zostać sprawdzony, i znowu myślę, że powinien. 1.2.3.4 jest prawidłowym IP, ale 1.2.3.4.5 nie jest i ani część 1.2.3.4, ani część 2.3.4.5 nie powinny skutkować dopasowaniem. Niektóre obawy można rozwiązać za pomocą tego wyrażenia:

grep -E '(^|[^[:alnum:]+)(([0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5])([^[:alnum:]]|$)' 

Niefortunne jest tutaj to, że część wyrażenia regularnego, która sprawdza poprawność oktetu, jest powtarzana, jak to ma miejsce w wielu oferowanych rozwiązaniach. Chociaż jest to lepsze niż w przypadku wzorców, powtórzenie można całkowicie wyeliminować, jeśli w używanym wyrażeniu regularnym obsługiwane są podprogramy. Następny przykład włącza te funkcje wraz z -Pprzełączaniem, grepa także wykorzystuje funkcje lookahead i lookbehind. (Nazwa funkcji, którą wybrałem, to „o” dla oktetu. Mógłbym użyć „oktetu” jako nazwy, ale chciałem być zwięzły.)

grep -P '(?<![\d\w\.])(?<o>([0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5]))(\.\g<o>){3}(?![\d\w\.])'

Obsługa kropki może faktycznie tworzyć fałszywe negatywy, jeśli adresy IP znajdują się w pliku z tekstem w formie zdań, ponieważ kropka może nadejść bez uwzględnienia kropkowanej notacji. Wariant powyższego rozwiązałby to:

grep -P '(?<![\d\w\.])(?<x>([0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5]))(\.\g<x>){3}(?!([\d\w]|\.\d))'

0
>>> my_hostname = "testhostn.ame"
>>> print bool(re.match("^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$", my_hostname))
True
>>> my_hostname = "testhostn....ame"
>>> print bool(re.match("^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$", my_hostname))
False
>>> my_hostname = "testhostn.A.ame"
>>> print bool(re.match("^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$", my_hostname))
True

0

Nowa struktura sieci ma niezawodne inicjatory dla struktur IPv4Address i struct IPv6Address, które bardzo łatwo obsługują część adresu IP. Robienie tego w IPv6 z wyrażeniem regularnym jest trudne ze wszystkimi zasadami skracania.

Niestety nie mam eleganckiej odpowiedzi na nazwę hosta.

Zauważ, że Framework sieci jest najnowszy, więc może zmusić cię do kompilacji dla najnowszych wersji systemu operacyjnego.

import Network
let tests = ["192.168.4.4","fkjhwojfw","192.168.4.4.4","2620:3","2620::33"]

for test in tests {
    if let _ = IPv4Address(test) {
        debugPrint("\(test) is valid ipv4 address")
    } else if let _ = IPv6Address(test) {
        debugPrint("\(test) is valid ipv6 address")
    } else {
        debugPrint("\(test) is not a valid IP address")
    }
}

output:
"192.168.4.4 is valid ipv4 address"
"fkjhwojfw is not a valid IP address"
"192.168.4.4.4 is not a valid IP address"
"2620:3 is not a valid IP address"
"2620::33 is valid ipv6 address"

-1

co powiesz na to?

([0-9]{1,3}\.){3}[0-9]{1,3}

I tak jest 9999999999.0.0.9999999999 :) Ale dla większości programistów to krótkie podejście wystarczy.
andreas

3
-1, ponieważ odpowiada to nonsensownym adresom IP (jak zauważa @Shebuka).
bdesham

-1

na php: filter_var(gethostbyname($dns), FILTER_VALIDATE_IP) == true ? 'ip' : 'not ip'


2
Chociaż ten kod może odpowiedzieć na pytanie, ogólnie wyjaśnienie obok kodu sprawia, że ​​odpowiedź jest znacznie bardziej użyteczna. Proszę edytować swoje odpowiedzi i dostarczyć pewien kontekst i wyjaśnienia.
user4642212,

I, chyba że się mylę, FILTER_VALIDATE_IP jest wartością tylko PHP.
DonGar

-2

Sprawdzanie nazw hostów, takich jak ... mywebsite.co.in, thangaraj.name, 18thangaraj.in, thangaraj106.in itp.,

[a-z\d+].*?\\.\w{2,4}$

3
-1. OP poprosił o coś „dobrze przetestowanego, aby dokładnie pasowało do najnowszych specyfikacji RFC”, ale to nie pasuje np. * .Museum, a będzie pasować do * .foo. Oto lista prawidłowych TLD.
bdesham

Nie jestem pewien, czy dobrym pomysłem jest umieszczenie plusa w klasie znaków (nawiasy kwadratowe), ponadto istnieją TLD z 5 literami ( na przykład .expert ).
Yaron

Najlepszym sposobem na osiągnięcie RFC jest użycie funkcji systemowych / językowych. inet_atonjest wystarczająco dobry.
m3nda

-2

Pomyślałem o tym prostym wzorcu dopasowania wyrażeń regularnych do dopasowywania adresów IP \ d + [.] \ D + [.] \ D + [.] \ D +


1111.1.1.1 nie jest prawidłowym adresem IP. Nie można naprawdę przetestować formatu IP, jeśli nie dbasz o podsieci. Powinieneś przynajmniej zadbać o liczbę występów z czymś podobnym ^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}i oczywiście nie będzie to właściwy sposób. Jeśli masz język do pisania skryptu, na pewno będziesz mieć dostęp do jego funkcji sieciowych. Najlepszym sposobem na sprawdzenie PRAWDZIWEGO adresu IP jest sprawdzenie, czy system przekonwertował i ip do właściwego formatu, a następnie sprawdzenie wartości true / false. W przypadku Pythona używam socket.inet_aton(ip). Przypadek PHP potrzebujesz inet_aton($ip).
m3nda

Użytkownicy Pythona mogą zobaczyć tutaj: gist.github.com/erm3nda/f25439bba66931d3ca9699b2816e796c
m3nda
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.