Comments 19
Для тех, кто хочет потестить где-то еще:
http://http://http://@http://http://?http://#http://
<пользователь http поменял пароль>
.NET тоже считает такой url валидным, но обрабатывает как-то странно:
var uri = new Uri(@"http://http://http://@http://http://?http://#http://");
Console.WriteLine(uri.Scheme); // http
// username и password вытащить нельзя, или я не нашёл как
Console.WriteLine(uri.Host); // http
Console.WriteLine(uri.Port); // 80 - это default port для http, по тому так и вышло
Console.WriteLine(uri.AbsolutePath); // //http://@http://http://
Console.WriteLine(uri.PathAndQuery); // //http://@http://http://?http://
Console.WriteLine(uri.Query); // ?http://
Console.WriteLine(uri.Fragment); // #http://
Что .NET, что упомянутый urllib, что Javascript парсят урлы примерно одинаково, не воспринимая понятие логина и пароля в URL как класс и соотв. интерпретируя их как домен и кусок пути.
А если проверить на well formed?
uri.UserInfo
https://docs.microsoft.com/ru-ru/dotnet/api/system.uri.userinfo?view=net-6.0#system-uri-userinfo
На хабре даже название статьи читается как URL:
Hidden text
![](https://habrastorage.org/getpro/habr/upload_files/4dd/920/54b/4dd92054b95aa8c5e30cd89ab44c8b98.png)
Можно ещё, path segment parameters прикрутить, наверное.
Go парсит оригинальный URL так же, как Python и JavaScript. Код:
Hidden text
package main
import (
"encoding/json"
"fmt"
"log"
"net/url"
)
func main() {
url, err := url.Parse("http://http://http://@http://http://?http://#http://")
if err != nil {
log.Fatal(err)
}
// конвертирую в JSON для красивого вывода
urlBytes, err := json.MarshalIndent(url, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(urlBytes))
}
Вывод:
Hidden text
{
"Scheme": "http",
"Opaque": "",
"User": null,
"Host": "http:",
"Path": "//http://@http://http://",
"RawPath": "",
"OmitHost": false,
"ForceQuery": false,
"RawQuery": "http://",
"Fragment": "http://",
"RawFragment": ""
}
Online: https://go.dev/play/p/q8qiLbkPriz
Если же, как указал автор, экранировать слэши в пароле, URL парсится так, как ожидалось изначально. Код:
Hidden text
package main
import (
"encoding/json"
"fmt"
"log"
"net/url"
)
func main() {
url, err := url.Parse("http://http:%2f%2fhttp:%2f%2f@http://http://?http://#http://")
if err != nil {
log.Fatal(err)
}
if url.User == nil {
log.Fatal("не могу распарсить данные пользователя")
}
// конвертирую в JSON для красивого вывода
urlBytes, err := json.MarshalIndent(url, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(urlBytes))
// структура url.Userinfo содержит только приватные поля, поэтому в JSON не попадёт; вывожу её отдельно
fmt.Printf("Username: %q\n", url.User.Username())
if password, ok := url.User.Password(); ok {
fmt.Printf("Password: %q\n", password)
}
}
Вывод:
Hidden text
{
"Scheme": "http",
"Opaque": "",
"User": {},
"Host": "http:",
"Path": "//http://",
"RawPath": "",
"OmitHost": false,
"ForceQuery": false,
"RawQuery": "http://",
"Fragment": "http://",
"RawFragment": ""
}
Username: "http"
Password: "//http://"
В свое время Windows 98 крэшилась при попытке открыть c:/con/con. Но это локально. Потом кто-то догадался на веб страничке добавить этот путь в качестве адреса для картинки. Открываешь интернет сайт и система крэшится.
URL это URI. В URI обязательными частями являются только scheme и path, части authority, query, fragment опциональные. Если опциональные части не проходят по формату - они игнорируются, если точнее интерпретируются как часть обязательной части (при прохождение по требованиям этой части). Тоже самое происходит и в формате опциональной части authority (тут обязательный только host).
Получается данный URI разбивается так:
host
┌─┴┐
http://http://http://@http://http://?http://#http://
└─┬┘ └──┬──┘└──────────┬───────────┘└───┬──┘└───┬──┘
scheme authority path query fragment
В итоге поведение Python, JavaScript, Go, .NET и браузеров является правильным и следует RFC (браузеры в дополнение ещё убирают лишние части, собственно по этому "съелось" двоеточие у не обязательного порта), поведение curl не соответствует RFC в полной мере.
Странно, что двоеточие без указания порта является валидным. Было бы логично считать опциональным всю часть":8080", а двоеточие без порта - ошибкой
Да господи, цветов нафигачили, скобочек понарисовали, а было достаточно записать его шаблонном виде без выёживаний
http://user:password@host:/?query#fragment
EBNF-схема есть, правила построения парсеров есть, ну да, глазу человека неприятно это видеть, но компу-то что, даже странно, откуда собственно проблема?
это - явно не валидный урл. Такое даже человеку не понять.
http://http://http://@http://http://?http://#http://