Hangi karakterler URL'yi geçersiz kılar?


515

Hangi karakterler URL'yi geçersiz kılar?

Bu geçerli URL'ler mi?

  • example.com/file[/].html
  • http://example.com/file[/].html

42
Doğrularken her zaman "olumlu düşün" gerekir: "geçerli olanı" isteyin, diğer her şey geçersizdir. (Birkaç) geçerli karaktere karşı test yapmak, olası tüm geçersiz karakterlerden çok daha güvenli (ve daha kolay!).
mfx

Yanıtlar:


600

Genel olarak RFC 3986 tarafından tanımlanan URI'lar (bkz. Bölüm 2: Karakterler ) aşağıdaki 84 karakterden herhangi birini içerebilir:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=

Bu listenin bu karakterlerin URI'de nerede olabileceğini belirtmediğini unutmayın.

Diğer karakterlerin yüzde kodlaması ( %hh) ile kodlanması gerekir . URI'nin her bir parçasının yüzde olarak kodlanmış bir sözcükle hangi karakterlerin temsil edilmesi gerektiği konusunda daha fazla kısıtlaması vardır.


31
(elbette, karakter listesi
uri'de

75
İşte dizenin tamamının yalnızca yukarıdaki karakterleri içerip içermediğini belirleyecek bir normal ifade: / ^ [! # $ & -; =? - [] _ ​​a-z ~] + $ /
Leif Wickland

43
@techiferous, Evet, kaçan karakterlere izin vermeyi unuttum. Daha çok benziyor olmalıydı: /^([!#$&-;=?-[]_a-z~]|%[0-9a-fA-F]{2})+$/ Kabul ettiğine karar verdiğiniz başka bir şey var mıydı? (Dize dize iyi oluşturulmuş URL içeren değilse, geçerli URL karakterler içeriyorsa Sadece bu regex sadece kontroller açık olmak.)
Leif Wickland

12
@Timwi RFC 3986, "Yüzde kodlu bir sekizli, bir karakter üçlüsü olarak kodlanır ve yüzde karakteri"% "ve ardından sekizlinin sayısal değerini temsil eden iki onaltılık sayıdan oluşur." Ayrıca, "Yüzde ("% ") karakteri yüzde kodlu sekizliler için gösterge görevi gördüğünden, bu sekizlinin bir URI içinde veri olarak kullanılabilmesi için yüzde"% 25 "olarak kodlanması gerekir." Bunu "%" ifadesinin yalnızca iki onaltılık basamak izliyorsa görünebileceğini söyleyerek okudum. Nasıl okuyorsun?
Leif Wickland

13
@ Weeble Normal ifadem bu karakterleri aralık kullanarak içeriyordu. '&' Ve ';' arasında ve '?' ve '[' görmediğin tüm karakterleri bulacaksın.
Leif Wickland

193

Biraz açıklama eklemek ve yukarıdaki soruyu doğrudan ele almak için, URL'ler ve URI'ler için sorunlara neden olan birkaç karakter sınıfı vardır.

İzin verilmeyen ve hiçbir zaman bir URL / URI'de, ayrılmış karakterlerde (aşağıda açıklanmıştır) ve bazı durumlarda sorunlara neden olabilecek, ancak "mantıksız" veya "güvensiz" olarak işaretlenmiş diğer karakterler vardır. Karakterlerin neden kısıtlandığına ilişkin açıklamalar RFC-1738 (URL'ler) ve RFC-2396'da (URI'ler) açıkça belirtilmiştir . Daha yeni RFC-3986'nın ( RFC-1738'e güncelleme) belirli bir bağlamda hangi karakterlere izin verildiğini tanımladığını, ancak eski özelliklerin aşağıdaki kurallarla hangi karakterlere izin verilmediğini daha basit ve daha genel bir açıklama sunduğunu unutmayın.

URI sözdizimi içinde izin verilmeyen ABD-ASCII Karakterlerine izin verilmiyor:

   control     = <US-ASCII coded characters 00-1F and 7F hexadecimal>
   space       = <US-ASCII coded character 20 hexadecimal>
   delims      = "<" | ">" | "#" | "%" | <">

"#" Karakteri, bir URI'yi parça tanımlayıcısından ayırmak için kullanıldığından hariç tutulur. Kaçan karakterlerin kodlanması için kullanıldığından "%" yüzde karakteri hariç tutulur. Başka bir deyişle, "#" ve "%" belirli bir bağlamda kullanılması gereken ayrılmış karakterlerdir.

Mantıksız karakterlerin listesine izin verilir, ancak sorunlara neden olabilir:

   unwise      = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"

Olan karakterler saklıdır sorgu bileşeni ve / veya içinde URI / URL içindeki özel bir anlamı vardır:

  reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","

Yukarıdaki "ayrılmış" sözdizimi sınıfı, bir URI içinde izin verilen, ancak genel URI sözdiziminin belirli bir bileşeni içinde izin verilmeyen karakterleri ifade eder. "Ayrılmış" kümesindeki karakterler tüm bağlamlarda ayrılmaz . Örneğin, ana bilgisayar adı isteğe bağlı bir kullanıcı adı içerebilir, böylece ftp://user@hostname/'@' karakterinin özel bir anlamı olduğu bir şey olabilir .

Geçersiz ve mantıksız karakterleri (ör. '$', '[', ']') Olan ve düzgün şekilde kodlanması gereken bir URL örneği:

http://mw1.google.com/mw-earth-vectordb/kml-samples/gp/seattle/gigapxl/$[level]/r$[y]_c$[x].jpg

URI'ler / URL'ler için bazı karakter kısıtlamaları programlama diline bağlıdır. Örneğin, '|' (0x7C) karakteri yalnızca URI spesifikasyonunda "unwise" olarak işaretlenmiş olmasına rağmen , Java java.net.URI yapıcısına bir URISyntaxException kurar , böylece benzer bir URL'ye izin verilmez ve bunun yerine Java'nın bir URI nesnesi örneğiyle kullanılması gibi kodlanması gerekir .http://api.google.com/q?exp=a|bhttp://api.google.com/q?exp=a%7Cb


2
Mükemmel, kapsamlı cevap, gerçek soruya doğrudan cevap veren tek kişi. Rezerve bölümünde, örneğin edebi çalışmalarını gerekebilir ?gayet güzel de ondan önce sorgu bölümünde, ancak imkansız ve ben sanmıyorum @bu listelerin herhangi aittir. Oh, ve %25son ip yerine, demek istemiyor musun %7C?
Bob Stein

1
Teşekkürler. İyi yakalama:% 25 örnekte bir yazım hatasıydı. Doğrudan RFC-2396'dan "ayrılmış" sözdizimi açıklamasına dipnot eklendi.
JasonM1

1
Bu cevap kötü değil , ancak bazı karışıklıklar ve hatalar var. Başlangıçta izin verilmeyen ve ayrılmış karakterleri (çok farklı şeyler) birleştirirsiniz, "akılsız" karakterler ve diğer izin verilmeyen karakterler (RFC 3986'ya bırakılır ve RFC 2396'da bile sözdizimsel olarak alakasız) arasındaki ayrımı çok fazla yaparsınız ve "bir sorgu bileşeni içinde" ayrılmış liste olarak ayrılmış tüm karakterler .
Mark Amery

1
Teşekkürler, izin verilmeyen ve aynı şekilde ayrılmış olanları gruplandırmak anlamına gelmiyordu. Yanıt güncellendi. RFC-2396'daki IMHO kurallarının daha eski olmasına rağmen, 3986'daki güncellenmiş kurallardan daha basittir. Yanıt, hangi içeriğe izin verildiği veya izin verilmediğinden ziyade genel olarak hangi karakterlerin sorunlu olabileceğini daha fazla yansıtır.
JasonM1

1
Tomcat'in son sürümlerde (7.0.73+, 8.0.39+, 8.5.7+), HTTP 400 hatalarıyla "unwise" kategorisindeki karakterlerle istekleri reddetmeye başlaması dikkat çekicidir: "İstek hedefinde geçersiz karakter bulundu. geçerli karakterler RFC 7230 ve RFC 3986'da tanımlanmıştır "
Philip

101

Buradaki mevcut cevapların çoğu pratik değildir, çünkü aşağıdaki gibi adreslerin gerçek dünya kullanımını tamamen görmezden gelirler:

İlk olarak, terminolojiye bir bakış. Ne olduğunu bu adresler? Geçerli URL'ler mi?

Tarihsel olarak, cevap "hayır" dı. RFC 3986'ya göre , 2005'ten itibaren bu tür adresler URI değildir (bu nedenle URL'ler bir tür URI olduğu için URL'ler değildir ). 2005 IETF standartlarının terminolojisine göre , teknik olarak URI olmayan ancak IRI'deki ASCII olmayan tüm karakterlerin yüzde kodlamasıyla URI'lara dönüştürülebilen RFC 3987'de tanımlandığı gibi, onlara IRI'ler (Uluslararası Kaynak Tanımlayıcıları) uygun şekilde çağırmalıyız. .

Modern teknik özelliklere göre, cevap "evet" tir. WHATWG Yaşam Standardı basitçe önce "URL'ler" olarak "URI'ları" veya "IRI'leri" denebilecek her şeyi sınıflandırır. Bu, belirtilen terminolojiyi, spesifikasyonu okumamış normal kişilerin, spesifikasyonun hedeflerinden biri olan "URL" kelimesini nasıl kullandığına uygun hale getirir .

WHATWG Yaşam Standardı kapsamında hangi karakterlere izin verilir?

"URL" nin bu yeni anlamı uyarınca, hangi karakterlere izin verilir? Böyle sorgu dizesi ve yol olarak URL'ye birçok yerinde, olarak, keyfi kullanım izni olan "URL birimleri" vardır,

URL kodu noktaları ve yüzde kodlu baytlar .

"URL kodu noktaları" nedir?

URL kod noktaları + 0021 U (!), + 0024 U ($), + 0026 U (&), + 0027 U ( '), U + 0028 sol parantez U + 0029 sağ parantez U + ASCII alfanümerik vardır 002A (*), U + 002B (+), U + 002C (,), U + 002D (-), U + 002E (.), U + 002F (/), U + 003A (:), U + 003B (;), U + 003D (=), U + 003F (?), U + 0040 (@), U + 005F (_), U + 007E (~) ve U + 00A0 ila U aralığındaki kod noktaları + 10FFFD, dahil, suretler ve karakter olmayanlar hariç.

("URL kod noktaları" listesinin içermediğini %, ancak %yüzde kodlama sırasının bir parçasıysa "URL kod birimleri" nde bunlara izin verildiğini unutmayın.)

Spesifikasyonun bu kümede olmayan herhangi bir karakterin kullanılmasına izin verdiği yeri tespit edebileceğim tek yer , IPv6 adreslerinin ve karakterlerin bulunduğu ana bilgisayardadır . URL'nin her yerinde, URL birimlerine veya daha da kısıtlayıcı bir karakter kümesine izin verilir.[]

Eski RFC'ler altında hangi karakterlere izin verildi?

Tarih uğruna ve buradaki cevaplarda başka bir yerde tam olarak araştırılmadığından, daha eski özelliklerin altında incelenmesine izin verelim.

Her şeyden önce, iki tür RFC 3986 ayrılmış karakteri var :

  • :/?#[]@RFC 3986'da tanımlanan bir URI için genel sözdiziminin bir parçası olan
  • !$&'()*+,;=RFC'nin genel sözdiziminin bir parçası olmayan ancak belirli URI şemalarının sözdizimsel bileşenleri olarak kullanılmak üzere ayrılmıştır. Örneğin, noktalı ve virgül sözdizimi bir parçası olarak kullanılan veri URI'lar ve &ve =her yerde bir parçası olarak kullanılan ?foo=bar&qux=baz(sorgu dizeleri biçiminde değildir RFC 3986 tarafından belirlenir).

Yukarıdaki ayrılmış karakterlerden herhangi biri, sözdizimsel amaçlarına hizmet etmek için kodlama yapmadan veya bu tür bir kullanımın sözdizimsel amacına hizmet eden karakter olarak yanlış yorumlanamayacağı bazı yerlerde verilerdeki değişmez karakterler olarak yasal olarak bir URI'de kullanılabilir. (Örneğin, /bir URL'de sözdizimsel anlamı olmasına rağmen , bir sorgu dizesinde kodlanmamış olarak kullanabilirsiniz, çünkü bir sorgu dizesinde anlamı yoktur .)

RFC 3986 de bazı belirten ayrılmamış hep Kodlama olmadan verileri temsil etmek basitçe kullanılabilecek karakterler,:

  • abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~

Son olarak, %yüzde kodlamaları için karakterin kendisine izin verilir.

Bu, yalnızca bir URL'de görünmesi yasaklanan aşağıdaki ASCII karakterlerini bırakır :

  • Yeni satır, sekme ve satır başını içeren kontrol karakterleri (karakter 0-1F ve 7F).
  • "<>\^`{|}

ASCII'deki diğer tüm karakterler yasal olarak bir URL'de yer alabilir.

Daha sonra RFC 3987, kaydedilmemiş karakter kümesini aşağıdaki unicode karakter aralıklarıyla genişletir:

  %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF
/ %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
/ %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
/ %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
/ %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
/ %xD0000-DFFFD / %xE1000-EFFFD

En son Unicode blok tanımları göz önüne alındığında, eski spesifikasyondaki bu blok seçimleri tuhaf ve keyfi görünüyor ; bunun nedeni muhtemelen RFC 3987'nin yazılmasından bu yana blokların on yıl içinde eklenmiş olmasıdır.


Son olarak, bir URL'de hangi karakterlerin yasal olarak görünebileceğini bilmenin, bazı karakterlerin yalnızca URL'nin belirli bölümlerinde yasal olması nedeniyle belirli bir dizenin yasal bir URL olup olmadığını anlamak için yeterli olmadığını belirtmek gerekebilir. Örneğin, ayrılmış karakterler [ve http: // [1080 :: 8: 800: 200C: 417A] / foo] gibi bir URL'deki IPv6 değişmez ana bilgisayarının bir parçası olarak yasaldır, ancak başka bir bağlamda yasal değildir. OP'nin örneği yasadışı.http://example.com/file[/].html


3
referanslar için plusone (örneğin, RFC)
Yan Foto

19

Ek sorunuzda www.example.com/file[/].htmlgeçerli bir URL olup olmadığını sordunuz .

Bir URL bir URI türü olduğu ve geçerli bir URI'nin şemaya sahip olması gerektiğinden bu URL geçerli değildir http:(bkz. RFC 3986 ).

http://www.example.com/file[/].htmlGeçerli bir URL olup olmadığını sormak istiyorsan , cevap hala hayır çünkü köşeli ayraç karakterleri orada geçerli değil.

Köşeli ayraç karakterleri şu biçimdeki URL'ler için ayrılmıştır: http://[2001:db8:85a3::8a2e:370:7334]/foo/bar(ana bilgisayar adı yerine IPv6 değişmez değeri)

Sorunu tam olarak anlamak istiyorsanız RFC 3986'yı dikkatlice okumaya değer.


RFC'yi okuduktan sonra @Stephen C ile daha ayrıntılı bir açıklama yapmaya daha meyilliyim.
skolima

URL'ler URI'nin bir alt kümesi değildir. [Ve ]gördüğüm neredeyse ayrıştırıcıları için geçerli bir URI değildir. Bu beni gerçek dünyaya
Adam Gent

@AdamGent URL'leri URI'lerin bir alt kümesidir. Aralarındaki tek fark, sözdizimsel değil, semantik bir ayrım olan kaynağın yerini tanımlayıp tanımlamamalarıdır. Kendilerini "URI" olarak etiketlediğini gördüğünüz ayrıştırıcılar köşeli parantezleri "URL" ayrıştırıcıları olarak etiketlenenlerden farklı bir şekilde ele aldıysa, URL'ler ve URI'ler arasındaki herhangi bir farktan kaynaklanmayan bu tamamen tesadüf.
Mark Amery

@ Mark Amery, C ++ 'nın C'nin bir üst kümesi olduğunu söylemeye benzer. Çoğunlukla doğru değil, ancak tamamen doğru değil çünkü (URL ve C) çok daha eski olan, daha az katı olan davranışları dahil etmek zorundalar. Sorun URL ayrıştırıcıları geçerli URI olmayan şeyleri ayrıştıracaktır ... Ve bunların çoğu (açıkçası bunu birçok dilde göstermekten çok yoruldum) Geriye dönük uyumluluk tesadüf değil. URL spesifikasyonunun en az daha eski olduğunu kabul edebilir miyiz?
Adam Gent

@MarkAmery Bu ayrıştırıcılar UnwiseURI'ler için çok ciddiye alacak ve yine de URL kütüphaneleri ile iyi olacak Python, C #, Java ve bazı C kütüphanelerinden. Görmezden gelinecek bir bayrak yok Unwise. URL'ler için Rust lang (bir tarayıcı için üretildiğinden beri ne yaptığını merak ediyorum) kontrol etmeliyim. Yine de çoğu tarayıcı mutlu bir şekilde "[", "]" iletecektir. Teoride C / C ++ ile söylediğim gibi alt / süper ama gerçek o kadar da doğru değil. Süper / altkümenin özelliklerinin ve anlambiliminin yorumlanmasına büyük ölçüde bağımlıdır.
Adam Gent

12

Bir URI'de kullanılabilen tüm geçerli karakterler ( URL , bir URI türüdür ) RFC 3986'da tanımlanmıştır .

Diğer tüm karakterler, önce "URL Kodlamalı" olmaları koşuluyla bir URL'de kullanılabilir. Bu, belirli "kodlar" için geçersiz karakterin değiştirilmesini içerir (genellikle yüzde sembolü (%) ve ardından onaltılı sayı biçiminde).

Bu bağlantı, HTML URL Kodlama Referansı , geçersiz karakterler için kodlamaların bir listesini içerir.


Ve için Unicode karakterleri, Wikipedia makalesi Yüzde kodlama bir URI gerekenlerden karakter verilerinin gösterimi için sağlayan yeni URI şemaları, aslında, çeviri olmadan ayrılmamış kümesindeki karakterleri temsil ettiğini "jenerik Uri sözdizimi görev,: şu diyor ve diğer tüm karakterleri UTF-8'e göre bayta dönüştürmeli ve sonra bu değerleri yüzde olarak kodlamalıdır . "
DavidRR

9

Unicode karakter aralıklarının birçoğu geçerli HTML5'tir , ancak yine de bunları kullanmak iyi bir fikir olmayabilir.

Örneğin, hrefdokümanlar http://www.w3.org/TR/html5/links.html#attr-hyperlink-href diyor :

A ve alan öğelerindeki href özelliği, potansiyel olarak boşluklarla çevrili geçerli bir URL olan bir değere sahip olmalıdır.

Sonra, "geçerli bir URL" noktalarının tanımı http://url.spec.whatwg.org/ diyor, bu amaçlar:

RFC 3986 ve RFC 3987'yi çağdaş uygulamalarla hizalayın ve bunları eski haline getirin.

Bu belge URL kodu noktalarını şu şekilde tanımlar :

ASCII alfasayısal, "!", "$", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/" , ":", ";", "=", "?", "@", "_", "~" ve U + 00A0 - U + D7FF, U + E000 - U + FDCF arasındaki kod noktaları , U + FDF0 - U + FFFD, U + 10000 - U + 1FFFD, U + 20000 - U + 2FFFD, U + 30000 - U + 3FFFD, U + 40000 - U + 4FFFD, U + 50000 - U + 5FFFD, U +60000 - U + 6FFFD, U + 70000 - U + 7FFFD, U + 80000 - U + 8FFFD, U + 90000 - U + 9FFFD, U + A0000 - U + AFFFD, U + B0000 - U + BFFFD, U + C0000 U + CFFFD, U + D0000 - U + DFFFD, U + E1000 - U + EFFFD, U + F0000 - U + FFFFD, U + 100000 - U + 10FFFD.

"URL kod noktaları" terimi daha sonra ifadede kullanılır:

C bir URL kodu noktası değilse ve "%" değilse, ayrıştırma hatası.

şema, yetki, göreceli yol, sorgu ve parça durumları da dahil olmak üzere ayrıştırma algoritmasının birkaç bölümünde: temelde URL'nin tamamı.

Ayrıca, doğrulayıcı http://validator.w3.org/ gibi URL'ler için geçer "你好"ve boşluk gibi karakterlere sahip URL'ler için geçmez"a b"

Tabii ki, Stephen C tarafından belirtildiği gibi, bu sadece karakterlerle değil, aynı zamanda bağlamla da ilgilidir: tüm algoritmayı anlamalısınız. Ancak "URL kod noktaları" sınıfı algoritmanın kilit noktalarında kullanıldığından, neyi kullanabileceğiniz veya kullanamayacağınız hakkında iyi bir fikir verir.

Ayrıca bkz: URL'lerde Unicode karakterler


5

URL URL'leri kendim bulunamadı karakterleri listesi oluşturmaya karar verdi, bu nedenle dize URL'leri bölmek için karakter seçmek gerekir:

>>> allowed = "-_.~!*'();:@&=+$,/?%#[]?@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
>>> from string import printable
>>> ''.join(set(printable).difference(set(allowed)))
'`" <\x0b\n\r\x0c\\\t{^}|>'

Yani, olası seçenekler satırsonu, sekme, boşluk, ters eğik çizgi ve "<>{}^|. Sanırım boşluk veya satırsonu ile gideceğim. :)


2

Sorunuza gerçekten bir cevap değil, ancak URL'leri doğrulamak gerçekten ciddi bir pide Muhtemelen sadece alan adını doğrulamaktan daha iyi ve URL'nin sorgu kısmını bırakın. Bu benim deneyimim. Ayrıca, URL'ye ping atmak ve geçerli bir yanıtla sonuçlanıp sonuçlanmadığını görmek için başvurabilirsiniz, ancak bu böyle basit bir görev için çok fazla olabilir.

URL'leri tespit etmek için düzenli ifadeler bol, google :)



Bu yanıt URL doğrulamasının normal ifade için değil, dile / platforma özgü bir kitaplık için bir iş olduğunu önerir .
DavidRR

0

Eski http (0.9, 1.0, 1.1) istek ve yanıt okuyucusu / yazarı uyguluyorum. İstek URI'si en sorunlu yerdir.

RFC 1738, 2396 veya 3986'yı olduğu gibi kullanamazsınız. Daha fazla karaktere izin veren birçok eski HTTP istemcisi ve sunucusu vardır. Yaptığım Yani araştırma yanlışlıkla yayınlanan web sunucusu erişim günlükleri dayalı: "GET URI HTTP/1.0" 200.

Aşağıdaki standart dışı karakterlerin URI'de sıklıkla kullanıldığını gördüm:

\ { } < > | ` ^ "

Bu karakterler RFC 1738'de güvensiz olarak tanımlanmıştır .

Tüm eski HTTP istemcileri ve sunucularıyla uyumlu olmak istiyorsanız - istek URI'sında bu karakterlere izin vermelisiniz .

Lütfen bu araştırma hakkında daha fazla bilgiyi http-og adresinde okuyun .


-4

Ben PHP metin için URL'leri çapa etiketleri dönüştürecek birkaç düzenli ifadeler ile geldi. (Önce tüm www. Url'leri http: // 'ye dönüştürür, sonra https?: // içeren tüm URL'leri bir href = ... html bağlantılarına dönüştürür

$string = preg_replace('/(https?:\/\/)([!#$&-;=?\-\[\]_a-z~%]+)/sim', '<a href="$1$2">$2</a>', preg_replace('/(\s)((www\.)([!#$&-;=?\-\[\]_a-z~%]+))/sim', '$1http://$2', $string) );


4
1; her ikisinin de bir miktar URL içerdiği gerçeğinin ötesinde, bunun sorulan soru ile ilgisi yoktur.
Mark Amery
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.