mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
AK: Add a basic URL class to help us handle URL's
We're gonna need these as we start to write more networking programs.
This commit is contained in:
parent
aaccf6ee4e
commit
ed43770b2f
Notes:
sideshowbarker
2024-07-19 12:46:11 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/ed43770b2f2
5 changed files with 223 additions and 1 deletions
|
@ -1,4 +1,4 @@
|
|||
PROGRAMS = TestString TestQueue TestVector TestHashMap TestJSON TestWeakPtr TestNonnullRefPtr TestRefPtr TestFixedArray TestFileSystemPath
|
||||
PROGRAMS = TestString TestQueue TestVector TestHashMap TestJSON TestWeakPtr TestNonnullRefPtr TestRefPtr TestFixedArray TestFileSystemPath TestURL
|
||||
|
||||
CXXFLAGS = -std=c++17 -Wall -Wextra -ggdb3 -O2 -I../ -I../../
|
||||
|
||||
|
@ -13,6 +13,7 @@ SHARED_TEST_OBJS = \
|
|||
../JsonValue.o \
|
||||
../JsonParser.o \
|
||||
../FileSystemPath.o \
|
||||
../URL.o \
|
||||
|
||||
.cpp.o:
|
||||
@echo "HOST_CXX $<"; $(PRE_CXX) $(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
@ -58,6 +59,9 @@ TestOptional: TestOptional.o $(SHARED_TEST_OBJS)
|
|||
TestFileSystemPath: TestFileSystemPath.o $(SHARED_TEST_OBJS)
|
||||
$(PRE_CXX) $(CXX) $(CXXFLAGS) -o $@ TestFileSystemPath.o $(SHARED_TEST_OBJS)
|
||||
|
||||
TestURL: TestURL.o $(SHARED_TEST_OBJS)
|
||||
$(PRE_CXX) $(CXX) $(CXXFLAGS) -o $@ TestURL.o $(SHARED_TEST_OBJS)
|
||||
|
||||
|
||||
clean:
|
||||
rm -f $(SHARED_TEST_OBJS)
|
||||
|
|
49
AK/Tests/TestURL.cpp
Normal file
49
AK/Tests/TestURL.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#include <AK/TestSuite.h>
|
||||
|
||||
#include <AK/URL.h>
|
||||
|
||||
TEST_CASE(construct)
|
||||
{
|
||||
EXPECT_EQ(URL().is_valid(), false);
|
||||
}
|
||||
|
||||
TEST_CASE(basic)
|
||||
{
|
||||
{
|
||||
URL url("http://www.serenityos.org/index.html");
|
||||
EXPECT_EQ(url.is_valid(), true);
|
||||
EXPECT_EQ(url.protocol(), "http");
|
||||
EXPECT_EQ(url.port(), 80);
|
||||
EXPECT_EQ(url.path(), "/index.html");
|
||||
}
|
||||
{
|
||||
URL url("https://localhost:1234/~anon/test/page.html");
|
||||
EXPECT_EQ(url.is_valid(), true);
|
||||
EXPECT_EQ(url.protocol(), "https");
|
||||
EXPECT_EQ(url.port(), 1234);
|
||||
EXPECT_EQ(url.path(), "/~anon/test/page.html");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(some_bad_urls)
|
||||
{
|
||||
EXPECT_EQ(URL("http:serenityos.org").is_valid(), false);
|
||||
EXPECT_EQ(URL("http:/serenityos.org").is_valid(), false);
|
||||
EXPECT_EQ(URL("http//serenityos.org").is_valid(), false);
|
||||
EXPECT_EQ(URL("http:///serenityos.org").is_valid(), false);
|
||||
EXPECT_EQ(URL("serenityos.org").is_valid(), false);
|
||||
EXPECT_EQ(URL("://serenityos.org").is_valid(), false);
|
||||
EXPECT_EQ(URL("http://serenityos.org:80:80/").is_valid(), false);
|
||||
EXPECT_EQ(URL("http://serenityos.org:80:80").is_valid(), false);
|
||||
EXPECT_EQ(URL("http://serenityos.org:abc").is_valid(), false);
|
||||
EXPECT_EQ(URL("http://serenityos.org:abc:80").is_valid(), false);
|
||||
EXPECT_EQ(URL("http://serenityos.org:abc:80/").is_valid(), false);
|
||||
EXPECT_EQ(URL("http://serenityos.org:/abc/").is_valid(), false);
|
||||
}
|
||||
|
||||
TEST_CASE(serialization)
|
||||
{
|
||||
EXPECT_EQ(URL("http://www.serenityos.org/").to_string(), "http://www.serenityos.org:80/");
|
||||
}
|
||||
|
||||
TEST_MAIN(URL)
|
130
AK/URL.cpp
Normal file
130
AK/URL.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <AK/URL.h>
|
||||
|
||||
namespace AK {
|
||||
|
||||
static inline bool is_valid_protocol_character(char ch)
|
||||
{
|
||||
return ch >= 'a' && ch <= 'z';
|
||||
}
|
||||
|
||||
static inline bool is_valid_hostname_character(char ch)
|
||||
{
|
||||
return ch && ch != '/' && ch != ':';
|
||||
}
|
||||
|
||||
static inline bool is_digit(char ch)
|
||||
{
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
bool URL::parse(const StringView& string)
|
||||
{
|
||||
enum class State {
|
||||
InProtocol,
|
||||
InHostname,
|
||||
InPort,
|
||||
InPath,
|
||||
};
|
||||
|
||||
Vector<char, 256> buffer;
|
||||
State state { State::InProtocol };
|
||||
|
||||
int index = 0;
|
||||
|
||||
auto peek = [&] {
|
||||
if (index >= string.length())
|
||||
return '\0';
|
||||
return string[index];
|
||||
};
|
||||
|
||||
auto consume = [&] {
|
||||
if (index >= string.length())
|
||||
return '\0';
|
||||
return string[index++];
|
||||
};
|
||||
|
||||
while (index < string.length()) {
|
||||
switch (state) {
|
||||
case State::InProtocol:
|
||||
if (is_valid_protocol_character(peek())) {
|
||||
buffer.append(consume());
|
||||
continue;
|
||||
}
|
||||
if (consume() != ':')
|
||||
return false;
|
||||
if (consume() != '/')
|
||||
return false;
|
||||
if (consume() != '/')
|
||||
return false;
|
||||
if (buffer.is_empty())
|
||||
return false;
|
||||
m_protocol = String::copy(buffer);
|
||||
buffer.clear();
|
||||
state = State::InHostname;
|
||||
continue;
|
||||
case State::InHostname:
|
||||
if (is_valid_hostname_character(peek())) {
|
||||
buffer.append(consume());
|
||||
continue;
|
||||
}
|
||||
if (buffer.is_empty())
|
||||
return false;
|
||||
m_host = String::copy(buffer);
|
||||
buffer.clear();
|
||||
if (peek() == ':') {
|
||||
consume();
|
||||
state = State::InPort;
|
||||
continue;
|
||||
}
|
||||
if (peek() == '/') {
|
||||
state = State::InPath;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
case State::InPort:
|
||||
if (is_digit(peek())) {
|
||||
buffer.append(consume());
|
||||
continue;
|
||||
}
|
||||
if (buffer.is_empty())
|
||||
return false;
|
||||
{
|
||||
bool ok;
|
||||
m_port = String::copy(buffer).to_uint(ok);
|
||||
buffer.clear();
|
||||
if (!ok)
|
||||
return false;
|
||||
}
|
||||
if (peek() == '/') {
|
||||
state = State::InPath;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
case State::InPath:
|
||||
buffer.append(consume());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
m_path = String::copy(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
URL::URL(const StringView& string)
|
||||
{
|
||||
m_valid = parse(string);
|
||||
}
|
||||
|
||||
String URL::to_string() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(m_protocol);
|
||||
builder.append("://");
|
||||
builder.append(m_host);
|
||||
builder.append(':');
|
||||
builder.append(String::number(m_port));
|
||||
builder.append(m_path);
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
}
|
38
AK/URL.h
Normal file
38
AK/URL.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/AKString.h>
|
||||
#include <AK/StringView.h>
|
||||
|
||||
namespace AK {
|
||||
|
||||
class URL {
|
||||
public:
|
||||
URL() {}
|
||||
URL(const StringView&);
|
||||
|
||||
bool is_valid() const { return m_valid; }
|
||||
String protocol() const { return m_protocol; }
|
||||
String host() const { return m_host; }
|
||||
String path() const { return m_path; }
|
||||
u16 port() const { return m_port; }
|
||||
|
||||
String to_string() const;
|
||||
|
||||
private:
|
||||
bool parse(const StringView&);
|
||||
|
||||
bool m_valid { false };
|
||||
u16 m_port { 80 };
|
||||
String m_protocol;
|
||||
String m_host;
|
||||
String m_path;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using AK::URL;
|
||||
|
||||
inline const LogStream& operator<<(const LogStream& stream, const URL& value)
|
||||
{
|
||||
return stream << value.to_string();
|
||||
}
|
|
@ -6,6 +6,7 @@ AK_OBJS = \
|
|||
../../AK/StringView.o \
|
||||
../../AK/StringBuilder.o \
|
||||
../../AK/FileSystemPath.o \
|
||||
../../AK/URL.o \
|
||||
../../AK/JsonValue.o \
|
||||
../../AK/JsonArray.o \
|
||||
../../AK/JsonObject.o \
|
||||
|
|
Loading…
Reference in a new issue