((
"NAMERD::s_CreateConnectorHttp(\"%s\")", iter->
name));
201 CORE_TRACEF((
"NAMERD::s_CreateConnectorMemory(\"%s\")", iter->
name));
215name, del ?
"Deleting":
"Releasing",
n,
221 if(n < --data->
n_cand) {
223(
data->n_cand -
n) *
sizeof(*
data->cand));
231 intall_standby = 1
;
232 doublemax_rate = 0.0;
242 for(
i= 0;
i<
data->n_cand; ++
i) {
243 if(max_rate < data->
cand[
i].
info->rate)
244max_rate =
data->cand[
i].info->rate;
251 for(
i=
data->n_cand;
i> 0; ) {
252 if(
data->cand[--
i].info->rate
268 for(
n= 0;
n<
data->n_cand; ++
n) {
273 ": \"%s\" %p", name,
n, infoname,
info));
283 if(
data->n_cand ==
data->a_cand) {
285 n=
data->a_cand + 10;
287? realloc(
data->cand,
n*
sizeof(*temp))
288:
malloc(
n*
sizeof(*temp)));
297name,
data->n_cand, infoname,
info));
311 const char* name,
size_t i)
314 # define mktime timegm 326(
"[%s] Unable to get JSON {\"addrs[" FMT_SIZE_T 327 "].meta.expires\"} value", name,
i));
331memset(&tm, 0,
sizeof(tm));
333 if(sscanf(
expires,
"%d-%d-%dT%d:%d:%d%c%n",
334&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
335&tm.tm_hour, &tm.tm_min, &tm.tm_sec,
337|| tm.tm_year < 2017 || tm.tm_year > 9999
338|| tm.tm_mon < 1 || tm.tm_mon > 12
339|| tm.tm_mday < 1 || tm.tm_mday > 31
340|| tm.tm_hour < 0 || tm.tm_hour > 23
341|| tm.tm_min < 0 || tm.tm_min > 59
342|| tm.tm_sec < 0 || tm.tm_sec > 60
345|| (tm.tm_year -= 1900,
348(exp = mktime(&tm)) == (time_t)(-1L))) {
351 "].meta.expires\"} value \"%s\"", name,
i,
expires));
360 if(tm.tm_isdst > 0 && h == tm.tm_hour)
368time_t diff = now - exp;
371 "].meta.expires\"} value expired: %s=" FMT_TIME_T " vs. " 373name,
i,
expires, exp, now, diff, &
"s"[diff==1]));
389 CORE_TRACEF((
"Enter NAMERD::s_ReadFullResponse(\"%s\")", name));
398(
"[%s] Failed to store response", name));
404 if(!(response = (
char*)
malloc(
len+ 1))) {
406(
"[%s] Failed to retrieve response", name));
410response[
len] =
'\0';
428 const char* response;
433 CORE_TRACEF((
"Enter NAMERD::s_ParseResponse(\"%s\")", iter->
name));
442(
"[%s] Response failed to parse as JSON", iter->
name));
447(
"[%s] Response is not a JSON object", iter->
name));
453(
"[%s] Unable to get JSON root object", iter->
name));
457 #if defined(_DEBUG) && ! defined(NDEBUG) 462json[
sizeof(json)-1] =
'\0';
466(
"[%s] Failed to serialize JSON", iter->
name));
475(
"[%s] Unable to get JSON {\"type\"} value", iter->
name));
479 CORE_TRACEF((
"[%s] Service unknown to NAMERD: \"%s\"",
488(
"[%s] Unable to get JSON {\"addrs\"} array",iter->
name));
496 for(
i= 0;
i<
n; ++
i) {
497 const char* host, *extra, *mime, *
mode, *
local,
498*privat, *stateful, *secure;
522 charkDescrFmt[] =
"%s %s:%u%s%s%s%s L=%s%s R=%.*lf%s T=%u%s";
539(
"[%s] Unable to get JSON {\"addrs[" FMT_SIZE_T 540 "]\"} object", iter->
name,
i));
546 if( ! host || ! *host) {
548(
"[%s] Unable to get JSON {\"addrs[" FMT_SIZE_T 549 "].ip\"} value", iter->
name,
i));
559(
"[%s] Unable to get JSON {\"addrs[" FMT_SIZE_T 560 "].port\"} type", iter->
name,
i));
564 if(port <= 0 || port > 65535) {
567 "].port\"} value %d", iter->
name,
i, port));
579 "].meta.serviceType\"} value \"%s\"", iter->
name,
i,
588 "].meta.serviceType\"} value \"%s\" (%u)", iter->
name,
603}
else if(sec != 1) {
606 "].meta.secure\"} value %d", iter->
name,
i, sec));
611 "].meta.secure\"} value for '%s' server" 612 " type ignored", iter->
name,
i,
type));
625}
else if(
st!= 1) {
628 "].meta.stateful\"} value %d", iter->
name,
i,
st));
633 "].meta.stateful\"} value for '%s' server" 634 " type ignored", iter->
name,
i,
type));
637stateful =
" S=Yes";
649(
"[%s] Unable to get JSON {\"addrs[" FMT_SIZE_T 650 "].meta.mode\"} value", iter->
name,
i));
658 local=
"Yes", privat =
" P=Yes";
661(
"[%s] Unrecognized JSON {\"addrs[" FMT_SIZE_T 662 "].meta.mode\"} value \"%s\"", iter->
name,
i,
669 "].meta.mode\"} private value for '%s' server" 670 " type ignored", iter->
name,
i,
type));
683 "].meta.rate\"} value %lf", iter->
name,
i, rate));
693(address,
"meta.expires"),
694(time_t) iter->
time, iter->
name,
i);
709 "].meta.contentType\"} value \"%s\" for '%s' server" 710 " type ignored", iter->
name,
i, mime,
type));
716 if( ! extra || ! *extra) {
720 "].meta.extra\"} for an HTTP type server",
726 "].meta.extra\"} for an NCBID type server",
735(
data->types && !(atype &
data->types))) {
736 CORE_TRACEF((
"[%s] Ignoring server info %s:%d with mismatching" 737 " server type '%s'(0x%04X) - allowed types = 0x%04X",
743 CORE_TRACEF((
"[%s] Ignoring stateful server info %s:%d '%s' in" 744 " stateless search", iter->
name, host, port,
type));
749 CORE_TRACEF((
"[%s] Ignoring %s server info %s:%d '%s' in" 750 " external search", iter->
name,
751*privat ?
"private":
"local", host, port,
type));
756 CORE_TRACEF((
"[%s] Ignoring regular server info %s:%d '%s' with" 757 " rate %.2lf in standby search", iter->
name,
758host, port,
type, rate));
763 size=
sizeof(kDescrFmt) + strlen(
type) + strlen(host) + 5
764+ strlen(extra) + (mime && *mime ? 3 + strlen(mime) : 0)
765+ strlen(
local) + strlen(privat) + 10
766+ strlen(stateful) + 10
+ 40
;
769(
"[%s] Failed to allocate for server" 770 " descriptor", iter->
name));
774sprintf(infostr, kDescrFmt,
type, host, port,
775&
" "[!(extra && *extra)], extra ? extra :
"",
776mime && *mime ?
" C=":
"", mime ? mime :
"",
779rate, stateful, time, secure) <
size);
782 CORE_TRACEF((
"[%s] NAMERD parsing server descriptor: \"%s\"",
783iter->
name, infostr));
785? iter->
name:
"", 0
);
788(
"[%s] Failed to parse server descriptor \"%s\"",
789iter->
name, infostr));
803(
"[%s] Failed to add server info", iter->
name));
818 free((
void*) response);
821found,
n,
data->n_cand));
822 returnfound ? 1
: 0
;
840sprintf(what,
"connection: %s",
IO_StatusStr(status));
842strcpy (what,
"connector");
844(
"[%s] Failed to create %s", iter->
name, what));
856retval ?
"success":
"failure"));
869 for(
i=
data->n_cand;
i> 0; ) {
871 if(
info->time < now) {
872 CORE_TRACEF((
"[%s] NAMERD expired server info (%u < %u): %p",
887 return n<
data->n_cand ? &
data->cand[
n] : 0;
901 if((!
data->n_cand && !
data->resolved)
907 if(!
data->n_cand &&
data->resolved) {
908 CORE_TRACEF((
"Leave NAMERD::s_GetNextInfo(\"%s\"): EOF", iter->
name));
924 CORE_TRACEF((
"Leave NAMERD::s_GetNextInfo(\"%s\"): \"%s\" %p", iter->
name,
941 for(
i= 0;
i<
data->n_cand; ++
i) {
944 data->cand[
i].info));
949 data->resolved = 0
;
978 size_tdtablen = strlen(dtab), arglen,
bufsize, dtab_in, dtab_out;
982 CORE_TRACEF((
"Enter NAMERD::x_UpdateDtabInArgs(\"%s\"): \"%s\"",
988 for( ; *arg && *arg !=
'#'; arg += arglen + !(arg[arglen] !=
'&')) {
989arglen = strcspn(arg,
"&#");
999 if(*arg && *arg !=
'#') {
1001 CORE_TRACEF((
"Found NAMERD::x_UpdateDtabInArgs(\"%s\") existing dtab" 1002 " \"%.*s\"", name, (
int) arglen, arg));
1007 bufsize= (arglen ? arglen + 3
: 0) + dtablen * 3 + 1
;
1010(
"[%s] Failed to %s service dtab %s\"%s\"", name,
1011arglen ?
"extend":
"allocate for",
1012arglen ?
"with ":
"", dtab));
1016memcpy(
buf, arg, arglen);
1017memcpy(
buf+ arglen,
"%3B", 3);
1028 buf[arglen + dtab_out] =
'\0';
1032(
"[%s] Failed to set service dtab \"%s\"",
1038 CORE_TRACEF((
"Leave NAMERD::x_UpdateDtabInArgs(\"%s\"): new dtab \"%s\"",
1051 CORE_TRACEF((
"Enter NAMERD::x_GetDtabFromHeader(\"%s\"): %s%s%s", name,
1052&
"\""[!header], header ? header :
"NULL", &
"\""[!header]));
1054 for(line = header; line && *line; line += linelen) {
1055 const char* end = strchr(line,
'\n');
1056linelen = end ? (size_t)(end - line) + 1 : strlen(line);
1057 if(!(end = (
const char*) memchr(line,
':', linelen)))
1066 while(linelen &&
isspace((
unsigned char)(*line))) {
1070 while(linelen &&
isspace((
unsigned char) line[linelen - 1]))
1072 CORE_TRACEF((
"Leave NAMERD::x_GetDtabFromHeader(\"%s\"): " 1074(
int) linelen, line));
1075 returnlinelen ?
strndup(line, linelen) : (
char*)(-1L);
1079 CORE_TRACEF((
"Leave NAMERD::x_GetDtabFromHeader(\"%s\"): " 1081 return(
char*)(-1L);
1088FILE*
fp= fopen(path,
"r");
1090 if(fgets(line, (
int)
size,
fp) && (
len= strlen(line)) > 0) {
1091 if(line[
len- 1] ==
'\n') {
1092 if(--
len&& line[
len- 1] ==
'\r')
1098line[
len] =
'\0';
1105 const unsigned char* s = (
const unsigned char*) src;
1106 unsigned char* d = (
unsigned char*) dst;
1115 char buf[80], x_buf[80];
1121(
"[%s] Unable to get NAMERD namespace", name));
1131(
"[%s] Unable to get NAMERD env", name));
1135 static void*
volatiles_Once = 0;
1150x_buf,
sizeof(x_buf)))) {
1152(
"[%s] Unable to get NAMERD location", name));
1163x_buf,
sizeof(x_buf)))) {
1165(
"[%s] Unable to get NAMERD role", name));
1170x_buf,
sizeof(x_buf), 0)) {
1172(
"[%s] Unable to get NAMERD protocol", name));
1179memcpy(
buf,
"dev", 4);
1182x_len = *x_buf ? 1 + strlen(x_buf) : 0;
1186nspc[
len++] =
'-';
1189nspc[
len] =
'\0';
1193(
"[%s] Unable to form NAMERD namespace in GC \"%s%s%s\"",
1194name,
buf, &
"-"[!x_len], x_buf));
1203(
"[%s] Unable to get NAMERD protocol", name));
1211(
"[%s] Unable to form NAMERD namespace \"%s\"",
1221 typedef void* (*FSvcCpy)(
void* dst,
const void* src,
size_t n);
1230 size_t len, argslen, namelen;
1241(
"[%s] Unable to get NAMERD scheme", iter->
name));
1252(
"[%s] Unrecognized NAMERD scheme \"%s\"",
1263(
"[%s] Unable to get NAMERD request method", iter->
name));
1274(
"[%s] Unrecognized NAMERD request method \"%s\"",
1285(
"[%s] Unable to get NAMERD HTTP version", iter->
name));
1296(
"[%s] Unable to get NAMERD host", iter->
name));
1302(
"[%s] %s NAMERD host \"%s\"", iter->
name,
1313(
"[%s] Unable to check NAMERD port", iter->
name));
1320(
"[%s] Bad NAMERD port \"%s\"", iter->
name,
buf));
1331(
"[%s] Unable to get NAMERD path", iter->
name));
1336(
"[%s] Failed to set NAMERD path \"%s\"", iter->
name,
1348(
"[%s] Failed to set NAMERD namespace \"%s\"",
1360(
"[%s] Unable to get NAMERD args", iter->
name));
1364argslen = strlen(
buf);
1365namelen = strlen(iter->
name);
1366 len= argslen + namelen;
1375 if(
len<
sizeof(
buf)) {
1395(
"[%s] Failed to set NAMERD args \"%s\"", iter->
name,
1410(
"[%s] Unable to get NAMERD HTTP proxy host",
1418(
"[%s] %s NAMERD HTTP proxy host \"%s\"", iter->
name,
1428(
"[%s] Unable to get NAMERD HTTP proxy port",
1435(
"[%s] %s NAMERD HTTP proxy port \"%s\"", iter->
name,
1436*
buf?
"Bad":
"Empty",
buf));
1453(
"[%s] Unable to get service dtab from HTTP header",
1457 if(dtab && dtab != (
const char*)(-1L)) {
1470(
"[%s] Unable to get NAMERD dtab", iter->
name));
1477(
"[%s] Failed to set NAMERD dtab in HTTP header",
1505 if(iter->
name[0] ==
'/') {
1507(
"[%s] Invalid NAMERD service name", iter->
name));
1514(
"[%s] NAMERD does not support Reverse-DNS service" 1515 " name resolutions, use at your own risk!", iter->
name));
1520(
"[%s] Failed to allocate for SNAMERD_Data",
1531 data->net_info ? 0 : errno,
1532(
"[%s] Failed to set up net_info", iter->
name));
1537 data->net_info->stateless = 1
;
1546 "User-Agent: NCBINamerdMapper" 1561(
"SERV_NAMERD_Open(\"%s%s%s%s%s\"): Service not found",
1564iter->
arg? iter->
arg:
"",
1566iter->
val? iter->
val:
""));
1574 CORE_TRACEF((
"Leave SERV_NAMERD_Open(\"%s\"): success", iter->
name));
1582 if( ! mock_body || ! *mock_body) {
std::ofstream out("events_result.xml")
main entry point for tests
static CS_CONNECTION * conn
static const struct type types[]
int BUF_Write(BUF *pBuf, const void *data, size_t size)
int BUF_Append(BUF *pBuf, const void *data, size_t size)
size_t BUF_Read(BUF buf, void *data, size_t size)
void BUF_Destroy(BUF buf)
CONNECTOR MEMORY_CreateConnectorEx(BUF buf, unsigned int own_buf)
EIO_Status CONN_Read(CONN conn, void *buf, size_t size, size_t *n_read, EIO_ReadMethod how)
struct SConnectorTag * CONNECTOR
connector handle
CONNECTOR HTTP_CreateConnector(const SConnNetInfo *net_info, const char *user_header, THTTP_Flags flags)
Same as HTTP_CreateConnector(net_info, flags, 0, 0, 0, 0) with the passed "user_header" overriding th...
EIO_Status CONN_Create(CONNECTOR connector, CONN *conn)
Same as CONN_CreateEx() called with 0 in the "flags" parameter.
FDestroy destroy
destroys handle, can be NULL
EIO_Status CONN_Close(CONN conn)
Close the connection and destroy all relevant internal data.
#define SERV_MINIMAL_RATE
const char * SERV_ReadType(const char *str, ESERV_Type *type)
#define SERV_MAXIMAL_RATE
const char * SERV_TypeStr(ESERV_Type type)
unsigned short TSERV_TypeOnly
Server type only, w/o specials.
int SERV_EqualInfo(const SSERV_Info *info1, const SSERV_Info *info2)
@ fSERV_Stateless
Stateless servers only.
char http_proxy_host[255+1]
int ConnNetInfo_ExtendUserHeader(SConnNetInfo *net_info, const char *header)
int ConnNetInfo_AddPath(SConnNetInfo *net_info, const char *path)
time_t UTIL_Timezone(void)
Return timezone offset (in seconds West of UTC) for the current time zone.
int ConnNetInfo_SetupStandardArgs(SConnNetInfo *net_info, const char *service)
unsigned short http_proxy_port
int ConnNetInfo_OverrideUserHeader(SConnNetInfo *net_info, const char *header)
SConnNetInfo * ConnNetInfo_Clone(const SConnNetInfo *net_info)
const char * ConnNetInfo_GetArgs(const SConnNetInfo *net_info)
int ConnNetInfo_PreOverrideArg(SConnNetInfo *net_info, const char *arg, const char *val)
const char * IO_StatusStr(EIO_Status status)
Get the text form of an enum status value.
int ConnNetInfo_PostOverrideArg(SConnNetInfo *net_info, const char *arg, const char *val)
const char * http_user_header
#define DEF_CONN_REG_SECTION
EBProxyType http_proxy_mask
void URL_Encode(const void *src_buf, size_t src_size, size_t *src_read, void *dst_buf, size_t dst_size, size_t *dst_written)
int ConnNetInfo_SetPath(SConnNetInfo *net_info, const char *path)
void ConnNetInfo_Destroy(SConnNetInfo *net_info)
@ eIO_Success
everything is fine, no error occurred
@ eIO_Unknown
unknown I/O error (likely fatal but can retry)
@ fProxy_Http
$http_proxy used
@ eFWMode_Adaptive
Regular firewall ports first, then fallback.
@ eIO_ReadPlain
read readily available data only, wait if none
unsigned int
A callback function used to compare two keys in a database.
const struct ncbi::grid::netcache::search::fields::SIZE size
const struct ncbi::grid::netcache::search::fields::EXPIRES expires
int strcmp(const char *str1, const char *str2)
int NCBI_HasSpaces(const char *s, size_t n)
Return non-zero(true) if a block of memory based at "s" and of size "n" has any space characters (as ...
#define LBSM_DEFAULT_RATE
#define LBSM_DEFAULT_TIME
#define LBSM_STANDBY_THRESHOLD
const char * ConnNetInfo_GetValueService(const char *service, const char *param, char *value, size_t value_size, const char *def_value)
size_t LB_Select(SERV_ITER iter, void *data, FGetCandidate get_candidate, double bonus)
#define REG_NAMERD_API_NAMESPACE
static int x_UpdateDtabInArgs(SConnNetInfo *net_info, const char *dtab, const char *name)
#define REG_NAMERD_API_REQ_METHOD
#define REG_NAMERD_API_SCHEME
static char * x_Namespace(char *nspc, size_t size, const char *name)
static void s_ProcessForStandby(struct SNAMERD_Data *data, const char *name)
static const char * s_ReadFullResponse(CONN conn, const char *name)
#define REG_NAMERD_API_ROLE
#define DEF_NAMERD_API_SCHEME
#define DEF_NAMERD_REG_SECTION
#define REG_NAMERD_API_ENV
static SLB_Candidate * s_GetCandidate(void *user_data, size_t n)
static CONNECTOR s_CreateConnectorHttp(SERV_ITER iter)
static void * x_memlwrcpy(void *dst, const void *src, size_t n)
#define DEF_NAMERD_API_ARGS
static TNCBI_Time x_ParseExpires(const char *expires, time_t now, const char *name, size_t i)
#define NAMERD_LOCAL_BONUS
static int s_AddServerInfo(struct SNAMERD_Data *data, SSERV_Info *info, const char *name)
static const char * x_ReadLine(const char *path, char *line, size_t size)
@ eNSub_Message
not an error
@ eNSub_Json
a JSON parsing failure
@ eNSub_TooLong
data was too long to fit in a buffer
@ eNSub_BadData
bad data was provided
@ eNSub_Alloc
memory allocation failed
@ eNSub_Connect
problem in connect library
static void s_Close(SERV_ITER)
#define REG_NAMERD_API_PORT
static int s_ParseResponse(SERV_ITER iter, CONN conn)
#define REG_NAMERD_PROXY_PORT
void *(* FSvcCpy)(void *dst, const void *src, size_t n)
static FCreateConnector s_CreateConnector
CONNECTOR(* FCreateConnector)(SERV_ITER iter)
#define REG_NAMERD_API_PATH
#define REG_NAMERD_API_HOST
static CONNECTOR s_CreateConnectorMemory(SERV_ITER iter)
#define DTAB_HTTP_HEADER_TAG
static char * x_GetDtabFromHeader(const char *header, const char *name)
#define DEF_NAMERD_API_REQ_METHOD
static void s_Reset(SERV_ITER)
static const SSERV_VTable kNamerdOp
static void s_RemoveServerInfo(struct SNAMERD_Data *data, size_t n, int del, const char *name)
#define DEF_NAMERD_API_HOST
#define DEF_NAMERD_API_PORT
#define DEF_NAMERD_PROXY_HOST
#define REG_NAMERD_PROXY_HOST
static int x_SetupConnectionParams(const SERV_ITER iter)
static const char * s_mock_body
#define REG_NAMERD_API_LOCATION
#define REG_NAMERD_API_HTTP_VERSION
static int s_Resolve(SERV_ITER iter)
#define DEF_NAMERD_PROXY_PORT
static int s_IsUpdateNeeded(struct SNAMERD_Data *data, TNCBI_Time now, const char *name)
#define DEF_NAMERD_API_HTTP_VERSION
static SSERV_Info * s_GetNextInfo(SERV_ITER, HOST_INFO *)
#define DEF_NAMERD_API_NAMESPACE
#define REG_NAMERD_API_PROTOCOL
const SSERV_VTable * SERV_NAMERD_Open(SERV_ITER iter, const SConnNetInfo *net_info, SSERV_Info **info)
int SERV_NAMERD_SetConnectorSource(const char *mock_body)
#define DEF_NAMERD_API_PATH
#define REG_NAMERD_API_ARGS
#define CORE_Once(once)
Return non-zero (true) if "*once" had a value of NULL, and set the value to non-NULL regardless (best...
unsigned int g_NCBI_ConnectRandomSeed
#define CORE_LOGF_X(subcode, level, fmt_args)
#define CORE_LOGF_ERRNO_X(subcode, level, error, fmt_args)
#define NCBI_CONNECT_SRAND_ADDEND
#define CORE_TRACEF(fmt_args)
SSERV_Info * SERV_ReadInfoEx(const char *str, const char *name, int lazy)
const char * SERV_NameOfInfo(const SSERV_Info *info)
ESERV_Type SERV_GetImplicitServerTypeInternal(const char *service)
double x_json_object_get_number(const x_JSON_Object *object, const char *name)
const char * x_json_object_get_string(const x_JSON_Object *object, const char *name)
x_JSON_Object * x_json_value_get_object(const x_JSON_Value *value)
void x_json_value_free(x_JSON_Value *value)
x_JSON_Status x_json_serialize_to_buffer_pretty(const x_JSON_Value *value, char *buf, size_t buf_size_in_bytes)
double x_json_object_dotget_number(const x_JSON_Object *object, const char *name)
x_JSON_Object * x_json_array_get_object(const x_JSON_Array *array, size_t index)
x_JSON_Value * x_json_parse_string(const char *string)
int x_json_object_dotget_boolean(const x_JSON_Object *object, const char *name)
x_JSON_Array * x_json_object_get_array(const x_JSON_Object *object, const char *name)
size_t x_json_array_get_count(const x_JSON_Array *array)
x_JSON_Value_Type x_json_value_get_type(const x_JSON_Value *value)
int x_json_object_dothas_value_of_type(const x_JSON_Object *object, const char *name, x_JSON_Value_Type type)
int x_json_object_has_value_of_type(const x_JSON_Object *object, const char *name, x_JSON_Value_Type type)
const char * x_json_object_dotget_string(const x_JSON_Object *object, const char *name)
static PCRE2_SIZE bufsize
static SLJIT_INLINE sljit_ins st(sljit_gpr r, sljit_s32 d, sljit_gpr x, sljit_gpr b)
voidp calloc(uInt items, uInt size)
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4