#pragma once #include #include #include #include #include #include namespace DB { /** Функции работы с URL. * Все функции работают не по RFC - то есть, максимально упрощены ради производительности. * * Функции, извлекающие часть URL-а. * Если в URL-е нет ничего похожего, то возвращается пустая строка. * * domain * domainWithoutWWW * topLevelDomain * protocol * path * queryString * fragment * queryStringAndFragment * * Функции, удаляющие часть из URL-а. * Если в URL-е нет ничего похожего, то URL остаётся без изменений. * * cutWWW * cutFragment * cutQueryString * cutQueryStringAndFragment * * Извлечь значение параметра в URL, если он есть. Вернуть пустую строку, если его нет. * Если таких параметров много - вернуть значение первого. Значение не разэскейпливается. * * extractURLParameter(URL, name) * * Извлечь все параметры из URL в виде массива строк вида name=value. * extractURLParameters(URL) * * Извлечь все имена параметров из URL в виде массива строк * extractURLParameterNames(URL) * * Убрать указанный параметр из URL. * cutURLParameter(URL, name) * * Получить массив иерархии URL. См. функцию nextURLInHierarchy в URLParser. * URLHierarchy(URL) */ typedef const char * Pos; struct ExtractProtocol { static size_t getReserveLengthForElement() { return strlen("https") + 1; } static void execute(Pos data, size_t size, Pos & res_data, size_t & res_size) { res_data = data; res_size = 0; Pos pos = data; while ((*pos >= 'a' && *pos <= 'z') || (*pos >= 'A' && *pos <= 'Z') || (*pos >= '0' && *pos <= '9')) ++pos; if (pos == data || pos + 3 >= data + size) return; if (pos[0] == ':') res_size = pos - data; } }; template struct ExtractDomain { static size_t getReserveLengthForElement() { return 15; } static void execute(Pos data, size_t size, Pos & res_data, size_t & res_size) { res_data = data; res_size = 0; Pos pos = data; Pos end = pos + size; Pos tmp; size_t protocol_length; ExtractProtocol::execute(data, size, tmp, protocol_length); pos += protocol_length + 3; if (pos[-1] != '/' || pos[-2] != '/') return; if (without_www && pos + 4 < end && !strncmp(pos, "www.", 4)) pos += 4; Pos domain_begin = pos; while (pos < end && *pos != '/' && *pos != ':' && *pos != '?' && *pos != '#') ++pos; if (pos == domain_begin) return; res_data = domain_begin; res_size = pos - domain_begin; } }; struct ExtractTopLevelDomain { static size_t getReserveLengthForElement() { return 5; } static void execute(Pos data, size_t size, Pos & res_data, size_t & res_size) { res_data = data; res_size = 0; Pos pos = data; Pos end = pos + size; Pos tmp; size_t protocol_length; ExtractProtocol::execute(data, size, tmp, protocol_length); pos += protocol_length + 3; if (pos[-1] != '/' || pos[-2] != '/') return; Pos domain_begin = pos; while (pos < end && *pos != '/' && *pos != ':' && *pos != '?' && *pos != '#') ++pos; if (pos == domain_begin) return; Pos last_dot = reinterpret_cast(memrchr(domain_begin, '.', pos - domain_begin)); if (!last_dot) return; /// Для IPv4-адресов не выделяем ничего. if (last_dot[1] <= '9') return; res_data = last_dot + 1; res_size = pos - res_data; } }; struct ExtractPath { static size_t getReserveLengthForElement() { return 25; } static void execute(Pos data, size_t size, Pos & res_data, size_t & res_size) { res_data = data; res_size = 0; Pos pos = data; Pos end = pos + size; if (nullptr != (pos = strchr(data, '/')) && pos[1] == '/' && nullptr != (pos = strchr(pos + 2, '/'))) { Pos query_string_or_fragment = strpbrk(pos, "?#"); res_data = pos; res_size = (query_string_or_fragment ? query_string_or_fragment : end) - res_data; } } }; template struct ExtractQueryString { static size_t getReserveLengthForElement() { return 10; } static void execute(Pos data, size_t size, Pos & res_data, size_t & res_size) { res_data = data; res_size = 0; Pos pos = data; Pos end = pos + size; if (nullptr != (pos = strchr(data, '?'))) { Pos fragment = strchr(pos, '#'); res_data = pos + (without_leading_char ? 1 : 0); res_size = (fragment ? fragment : end) - res_data; } } }; template struct ExtractFragment { static size_t getReserveLengthForElement() { return 10; } static void execute(Pos data, size_t size, Pos & res_data, size_t & res_size) { res_data = data; res_size = 0; Pos pos = data; Pos end = pos + size; if (nullptr != (pos = strchr(data, '#'))) { res_data = pos + (without_leading_char ? 1 : 0); res_size = end - res_data; } } }; template struct ExtractQueryStringAndFragment { static size_t getReserveLengthForElement() { return 20; } static void execute(Pos data, size_t size, Pos & res_data, size_t & res_size) { res_data = data; res_size = 0; Pos pos = data; Pos end = pos + size; if (nullptr != (pos = strchr(data, '?'))) { res_data = pos + (without_leading_char ? 1 : 0); res_size = end - res_data; } } }; /// С точкой на конце. struct ExtractWWW { static void execute(Pos data, size_t size, Pos & res_data, size_t & res_size) { res_data = data; res_size = 0; Pos pos = data; Pos end = pos + size; Pos tmp; size_t protocol_length; ExtractProtocol::execute(data, size, tmp, protocol_length); pos += protocol_length + 3; if (pos[-1] != '/' || pos[-2] != '/') return; if (pos + 4 < end && !strncmp(pos, "www.", 4)) { res_data = pos; res_size = 4; } } }; struct ExtractURLParameterImpl { static void vector(const ColumnString::Chars_t & data, const ColumnString::Offsets_t & offsets, std::string pattern, ColumnString::Chars_t & res_data, ColumnString::Offsets_t & res_offsets) { res_data.reserve(data.size() / 5); res_offsets.resize(offsets.size()); pattern += '='; const char * param_str = pattern.c_str(); size_t param_len = pattern.size(); size_t prev_offset = 0; size_t res_offset = 0; for (size_t i = 0; i < offsets.size(); ++i) { size_t cur_offset = offsets[i]; const char * pos = nullptr; do { const char * str = reinterpret_cast(&data[prev_offset]); const char * begin = strchr(str, '?'); if (begin == nullptr) break; pos = strstr(begin + 1, param_str); if (pos == nullptr) break; if (pos != begin + 1 && *(pos - 1) != ';' && *(pos - 1) != '&') { pos = nullptr; break; } pos += param_len; } while (false); if (pos != nullptr) { const char * end = strpbrk(pos, "&;#"); if (end == nullptr) end = pos + strlen(pos); res_data.resize(res_offset + (end - pos) + 1); memcpy(&res_data[res_offset], pos, end - pos); res_offset += end - pos; } else { res_data.resize(res_offset + 1); } res_data[res_offset] = 0; ++res_offset; res_offsets[i] = res_offset; prev_offset = cur_offset; } } }; struct CutURLParameterImpl { static void vector(const ColumnString::Chars_t & data, const ColumnString::Offsets_t & offsets, std::string pattern, ColumnString::Chars_t & res_data, ColumnString::Offsets_t & res_offsets) { res_data.reserve(data.size()); res_offsets.resize(offsets.size()); pattern += '='; const char * param_str = pattern.c_str(); size_t param_len = pattern.size(); size_t prev_offset = 0; size_t res_offset = 0; for (size_t i = 0; i < offsets.size(); ++i) { size_t cur_offset = offsets[i]; const char * url_begin = reinterpret_cast(&data[prev_offset]); const char * url_end = reinterpret_cast(&data[cur_offset]) - 1; const char * begin_pos = url_begin; const char * end_pos = begin_pos; do { const char * begin = strchr(url_begin, '?'); if (begin == nullptr) break; const char * pos = strstr(begin + 1, param_str); if (pos == nullptr) break; if (pos != begin + 1 && *(pos - 1) != ';' && *(pos - 1) != '&') { pos = nullptr; break; } begin_pos = pos; end_pos = begin_pos + param_len; /// Пропустим значение. while (*end_pos && *end_pos != ';' && *end_pos != '&' && *end_pos != '#') ++end_pos; /// Захватим ';' или '&' до или после параметра. if (*end_pos == ';' || *end_pos == '&') ++end_pos; else if (*(begin_pos - 1) == ';' || *(begin_pos - 1) == '&') --begin_pos; } while (false); size_t cut_length = (url_end - url_begin) - (end_pos - begin_pos); res_data.resize(res_offset + cut_length + 1); memcpy(&res_data[res_offset], url_begin, begin_pos - url_begin); memcpy(&res_data[res_offset] + (begin_pos - url_begin), end_pos, url_end - end_pos); res_offset += cut_length + 1; res_data[res_offset - 1] = 0; res_offsets[i] = res_offset; prev_offset = cur_offset; } } }; class ExtractURLParametersImpl { private: Pos pos; Pos end; bool first; public: static String getName() { return "extractURLParameters"; } static void checkArguments(const DataTypes & arguments) { if (arguments.size() != 1) throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size()) + ", should be 1.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); if (!dynamic_cast(&*arguments[0])) throw Exception("Illegal type " + arguments[0]->getName() + " of first argument of function " + getName() + ". Must be String.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); } void init(Block & block, const ColumnNumbers & arguments) {} /// Возвращает позицию аргумента, являющегося столбцом строк size_t getStringsArgumentPosition() { return 0; } /// Вызывается для каждой следующей строки. void set(Pos pos_, Pos end_) { pos = pos_; end = end_; first = true; } /// Получить следующий токен, если есть, или вернуть false. bool get(Pos & token_begin, Pos & token_end) { if (pos == nullptr) return false; if (first) { first = false; pos = strchr(pos, '?'); if (pos == nullptr) return false; ++pos; } token_begin = pos; pos = strchr(pos, '='); if (pos == nullptr) return false; ++pos; pos = strpbrk(pos, "&;#"); if (pos == nullptr) token_end = end; else token_end = pos++; return true; } }; class ExtractURLParameterNamesImpl { private: Pos pos; Pos end; bool first; public: static String getName() { return "extractURLParameterNames"; } static void checkArguments(const DataTypes & arguments) { if (arguments.size() != 1) throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size()) + ", should be 1.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); if (!dynamic_cast(&*arguments[0])) throw Exception("Illegal type " + arguments[0]->getName() + " of first argument of function " + getName() + ". Must be String.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); } /// Возвращает позицию аргумента, являющегося столбцом строк size_t getStringsArgumentPosition() { return 0; } void init(Block & block, const ColumnNumbers & arguments) {} /// Вызывается для каждой следующей строки. void set(Pos pos_, Pos end_) { pos = pos_; end = end_; first = true; } /// Получить следующий токен, если есть, или вернуть false. bool get(Pos & token_begin, Pos & token_end) { if (pos == nullptr) return false; if (first) { first = false; pos = strchr(pos, '?'); } else pos = strchr(pos, '&'); if (pos == nullptr) return false; ++pos; token_begin = pos; pos = strchr(pos, '='); if (pos == nullptr) return false; else token_end = pos++; return true; } }; class URLHierarchyImpl { private: Pos begin; Pos pos; Pos end; public: static String getName() { return "URLHierarchy"; } static void checkArguments(const DataTypes & arguments) { if (arguments.size() != 1) throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size()) + ", should be 1.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); if (!dynamic_cast(&*arguments[0])) throw Exception("Illegal type " + arguments[0]->getName() + " of first argument of function " + getName() + ". Must be String.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); } void init(Block & block, const ColumnNumbers & arguments) {} /// Возвращает позицию аргумента, являющегося столбцом строк size_t getStringsArgumentPosition() { return 0; } /// Вызывается для каждой следующей строки. void set(Pos pos_, Pos end_) { begin = pos = pos_; end = end_; } /// Получить следующий токен, если есть, или вернуть false. bool get(Pos & token_begin, Pos & token_end) { /// Код из URLParser. if (pos == end) return false; if (pos == begin) { /// Распарсим всё, что идёт до пути /// Предположим, что протокол уже переведён в нижний регистр. while (pos < end && ((*pos > 'a' && *pos < 'z') || (*pos > '0' && *pos < '9'))) ++pos; /** Будем вычислять иерархию только для URL-ов, в которых есть протокол, и после него идут два слеша. * (http, file - подходят, mailto, magnet - не подходят), и после двух слешей ещё хоть что-нибудь есть * Для остальных просто вернём полный URL как единственный элемент иерархии. */ if (pos == begin || pos == end || !(*pos++ == ':' && pos < end && *pos++ == '/' && pos < end && *pos++ == '/' && pos < end)) { pos = end; token_begin = begin; token_end = end; return true; } /// Доменом для простоты будем считать всё, что после протокола и двух слешей, до следующего слеша или до ? или до # while (pos < end && !(*pos == '/' || *pos == '?' || *pos == '#')) ++pos; if (pos != end) ++pos; token_begin = begin; token_end = pos; return true; } /// Идём до следующего / или ? или #, пропуская все те, что вначале. while (pos < end && (*pos == '/' || *pos == '?' || *pos == '#')) ++pos; if (pos == end) return false; while (pos < end && !(*pos == '/' || *pos == '?' || *pos == '#')) ++pos; if (pos != end) ++pos; token_begin = begin; token_end = pos; return true; } }; class URLPathHierarchyImpl { private: Pos begin; Pos pos; Pos end; Pos start; public: static String getName() { return "URLPathHierarchy"; } static void checkArguments(const DataTypes & arguments) { if (arguments.size() != 1) throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size()) + ", should be 1.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); if (!dynamic_cast(&*arguments[0])) throw Exception("Illegal type " + arguments[0]->getName() + " of first argument of function " + getName() + ". Must be String.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); } void init(Block & block, const ColumnNumbers & arguments) {} /// Возвращает позицию аргумента, являющегося столбцом строк size_t getStringsArgumentPosition() { return 0; } /// Вызывается для каждой следующей строки. void set(Pos pos_, Pos end_) { begin = pos = pos_; start = begin; end = end_; } /// Получить следующий токен, если есть, или вернуть false. bool get(Pos & token_begin, Pos & token_end) { /// Код из URLParser. if (pos == end) return false; if (pos == begin) { /// Распарсим всё, что идёт до пути /// Предположим, что протокол уже переведён в нижний регистр. while (pos < end && ((*pos > 'a' && *pos < 'z') || (*pos > '0' && *pos < '9'))) ++pos; /** Будем вычислять иерархию только для URL-ов, в которых есть протокол, и после него идут два слеша. * (http, file - подходят, mailto, magnet - не подходят), и после двух слешей ещё хоть что-нибудь есть * Для остальных просто вернём пустой массив. */ if (pos == begin || pos == end || !(*pos++ == ':' && pos < end && *pos++ == '/' && pos < end && *pos++ == '/' && pos < end)) { pos = end; return false; } /// Доменом для простоты будем считать всё, что после протокола и двух слешей, до следующего слеша или до ? или до # while (pos < end && !(*pos == '/' || *pos == '?' || *pos == '#')) ++pos; start = pos; if (pos != end) ++pos; } /// Идём до следующего / или ? или #, пропуская все те, что вначале. while (pos < end && (*pos == '/' || *pos == '?' || *pos == '#')) ++pos; if (pos == end) return false; while (pos < end && !(*pos == '/' || *pos == '?' || *pos == '#')) ++pos; if (pos != end) ++pos; token_begin = start; token_end = pos; return true; } }; /** Выделить кусок строки, используя Extractor. */ template struct ExtractSubstringImpl { static void vector(const ColumnString::Chars_t & data, const ColumnString::Offsets_t & offsets, ColumnString::Chars_t & res_data, ColumnString::Offsets_t & res_offsets) { size_t size = offsets.size(); res_offsets.resize(size); res_data.reserve(size * Extractor::getReserveLengthForElement()); size_t prev_offset = 0; size_t res_offset = 0; /// Выделенный кусок. Pos start; size_t length; for (size_t i = 0; i < size; ++i) { Extractor::execute(reinterpret_cast(&data[prev_offset]), offsets[i] - prev_offset - 1, start, length); res_data.resize(res_data.size() + length + 1); memcpy(&res_data[res_offset], start, length); res_offset += length + 1; res_data[res_offset - 1] = 0; res_offsets[i] = res_offset; prev_offset = offsets[i]; } } static void constant(const std::string & data, std::string & res_data) { Pos start; size_t length; Extractor::execute(data.data(), data.size(), start, length); res_data.assign(start, length); } static void vector_fixed(const ColumnString::Chars_t & data, size_t n, ColumnString::Chars_t & res_data) { throw Exception("Column of type FixedString is not supported by URL functions", ErrorCodes::ILLEGAL_COLUMN); } }; /** Удалить кусок строки, используя Extractor. */ template struct CutSubstringImpl { static void vector(const ColumnString::Chars_t & data, const ColumnString::Offsets_t & offsets, ColumnString::Chars_t & res_data, ColumnString::Offsets_t & res_offsets) { res_data.reserve(data.size()); size_t size = offsets.size(); res_offsets.resize(size); size_t prev_offset = 0; size_t res_offset = 0; /// Выделенный кусок. Pos start; size_t length; for (size_t i = 0; i < size; ++i) { const char * current = reinterpret_cast(&data[prev_offset]); Extractor::execute(current, offsets[i] - prev_offset - 1, start, length); size_t start_index = start - reinterpret_cast(&data[0]); res_data.resize(res_data.size() + offsets[i] - prev_offset - length); memcpy(&res_data[res_offset], current, start - current); memcpy(&res_data[res_offset + start - current], start + length, offsets[i] - start_index - length); res_offset += offsets[i] - prev_offset - length; res_offsets[i] = res_offset; prev_offset = offsets[i]; } } static void constant(const std::string & data, std::string & res_data) { Pos start; size_t length; Extractor::execute(data.data(), data.size(), start, length); res_data.erase(start - data.data(), length); } static void vector_fixed(const ColumnString::Chars_t & data, size_t n, ColumnString::Chars_t & res_data) { throw Exception("Column of type FixedString is not supported by URL functions", ErrorCodes::ILLEGAL_COLUMN); } }; struct NameProtocol { static const char * get() { return "protocol"; } }; struct NameDomain { static const char * get() { return "domain"; } }; struct NameDomainWithoutWWW { static const char * get() { return "domainWithoutWWW"; } }; struct NameTopLevelDomain { static const char * get() { return "topLevelDomain"; } }; struct NamePath { static const char * get() { return "path"; } }; struct NameQueryString { static const char * get() { return "queryString"; } }; struct NameFragment { static const char * get() { return "fragment"; } }; struct NameQueryStringAndFragment { static const char * get() { return "queryStringAndFragment"; } }; struct NameCutWWW { static const char * get() { return "cutWWW"; } }; struct NameCutQueryString { static const char * get() { return "cutQueryString"; } }; struct NameCutFragment { static const char * get() { return "cutFragment"; } }; struct NameCutQueryStringAndFragment { static const char * get() { return "cutQueryStringAndFragment"; } }; struct NameExtractURLParameter { static const char * get() { return "extractURLParameter"; } }; struct NameCutURLParameter { static const char * get() { return "cutURLParameter"; } }; typedef FunctionStringToString, NameProtocol> FunctionProtocol; typedef FunctionStringToString >, NameDomain> FunctionDomain; typedef FunctionStringToString >, NameDomainWithoutWWW> FunctionDomainWithoutWWW; typedef FunctionStringToString, NameTopLevelDomain> FunctionTopLevelDomain; typedef FunctionStringToString, NamePath> FunctionPath; typedef FunctionStringToString >, NameQueryString> FunctionQueryString; typedef FunctionStringToString >, NameFragment> FunctionFragment; typedef FunctionStringToString >, NameQueryStringAndFragment> FunctionQueryStringAndFragment; typedef FunctionStringToString, NameCutWWW> FunctionCutWWW; typedef FunctionStringToString >, NameCutQueryString> FunctionCutQueryString; typedef FunctionStringToString >, NameCutFragment> FunctionCutFragment; typedef FunctionStringToString >, NameCutQueryStringAndFragment> FunctionCutQueryStringAndFragment; typedef FunctionsStringSearchToString FunctionExtractURLParameter; typedef FunctionsStringSearchToString FunctionCutURLParameter; typedef FunctionTokens FunctionExtractURLParameters; typedef FunctionTokens FunctionExtractURLParameters; typedef FunctionTokens FunctionURLHierarchy; typedef FunctionTokens FunctionURLPathHierarchy; typedef FunctionTokens FunctionExtractURLParameterNames; }