mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-10 21:23:58 +01:00
Split directory_handle_command_get into subfunctions.
This was one of our longest functions, at 600 lines. It makes a nice table-driven URL-based function instead. The code is a bit ugly, it leave the indentation as it is in hopes of making pending directory.c changes easier to merge. Later we can clean up the indentation. Also, remove unused mallinfo export code from directory.c Closes ticket 16698
This commit is contained in:
parent
84af12e9db
commit
50d777dcf4
@ -2854,6 +2854,73 @@ choose_compression_level(ssize_t n_bytes)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Information passed to handle a GET request. */
|
||||||
|
typedef struct get_handler_args_t {
|
||||||
|
/** True if the client asked for compressed data. */
|
||||||
|
int compressed;
|
||||||
|
/** If nonzero, the time included an if-modified-since header with this
|
||||||
|
* value. */
|
||||||
|
time_t if_modified_since;
|
||||||
|
/** String containing the requested URL or resource. */
|
||||||
|
const char *url;
|
||||||
|
/** String containing the HTTP headers */
|
||||||
|
const char *headers;
|
||||||
|
} get_handler_args_t;
|
||||||
|
|
||||||
|
/** Entry for handling an HTTP GET request.
|
||||||
|
*
|
||||||
|
* This entry matches a request if "string" is equal to the requested
|
||||||
|
* resource, or if "is_prefix" is true and "string" is a prefix of the
|
||||||
|
* requested resource.
|
||||||
|
*
|
||||||
|
* The 'handler' function is called to handle the request. It receives
|
||||||
|
* an arguments structure, and must return 0 on success or -1 if we should
|
||||||
|
* close the connection.
|
||||||
|
**/
|
||||||
|
typedef struct url_table_ent_s {
|
||||||
|
const char *string;
|
||||||
|
int is_prefix;
|
||||||
|
int (*handler)(dir_connection_t *conn, const get_handler_args_t *args);
|
||||||
|
} url_table_ent_t;
|
||||||
|
|
||||||
|
static int handle_get_frontpage(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
static int handle_get_current_consensus(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
static int handle_get_status_vote(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
static int handle_get_microdesc(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
static int handle_get_descriptor(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
static int handle_get_keys(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
static int handle_get_rendezvous2(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
static int handle_get_bytes(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
static int handle_get_robots(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
static int handle_get_networkstatus_bridges(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args);
|
||||||
|
|
||||||
|
/** Table for handling GET requests. */
|
||||||
|
static const url_table_ent_t url_table[] = {
|
||||||
|
{ "/tor/", 0, handle_get_frontpage },
|
||||||
|
{ "/tor/status-vote/current/consensus", 1, handle_get_current_consensus },
|
||||||
|
{ "/tor/status-vote/current/", 1, handle_get_status_vote },
|
||||||
|
{ "/tor/status-vote/next/", 1, handle_get_status_vote },
|
||||||
|
{ "/tor/micro/d/", 1, handle_get_microdesc },
|
||||||
|
{ "/tor/server/", 1, handle_get_descriptor },
|
||||||
|
{ "/tor/extra/", 1, handle_get_descriptor },
|
||||||
|
{ "/tor/keys/", 1, handle_get_keys },
|
||||||
|
{ "/tor/rendezvous2/", 1, handle_get_rendezvous2 },
|
||||||
|
{ "/tor/bytes.txt", 0, handle_get_bytes },
|
||||||
|
{ "/tor/robots.txt", 0, handle_get_robots },
|
||||||
|
{ "/tor/networkstatus-bridges", 0, handle_get_networkstatus_bridges },
|
||||||
|
{ NULL, 0, NULL },
|
||||||
|
};
|
||||||
|
|
||||||
/** Helper function: called when a dirserver gets a complete HTTP GET
|
/** Helper function: called when a dirserver gets a complete HTTP GET
|
||||||
* request. Look for a request for a directory or for a rendezvous
|
* request. Look for a request for a directory or for a rendezvous
|
||||||
* service descriptor. On finding one, write a response into
|
* service descriptor. On finding one, write a response into
|
||||||
@ -2863,9 +2930,7 @@ STATIC int
|
|||||||
directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
||||||
const char *req_body, size_t req_body_len)
|
const char *req_body, size_t req_body_len)
|
||||||
{
|
{
|
||||||
size_t dlen;
|
|
||||||
char *url, *url_mem, *header;
|
char *url, *url_mem, *header;
|
||||||
const or_options_t *options = get_options();
|
|
||||||
time_t if_modified_since = 0;
|
time_t if_modified_since = 0;
|
||||||
int compressed;
|
int compressed;
|
||||||
size_t url_len;
|
size_t url_len;
|
||||||
@ -2905,10 +2970,46 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
url_len -= 2;
|
url_len -= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp(url,"/tor/")) {
|
get_handler_args_t args;
|
||||||
|
args.url = url;
|
||||||
|
args.headers = headers;
|
||||||
|
args.if_modified_since = if_modified_since;
|
||||||
|
args.compressed = compressed;
|
||||||
|
|
||||||
|
int i, result = -1;
|
||||||
|
for (i = 0; url_table[i].string; ++i) {
|
||||||
|
int match;
|
||||||
|
if (url_table[i].is_prefix) {
|
||||||
|
match = !strcmpstart(url, url_table[i].string);
|
||||||
|
} else {
|
||||||
|
match = !strcmp(url, url_table[i].string);
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
result = url_table[i].handler(conn, &args);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we didn't recognize the url */
|
||||||
|
write_http_status_line(conn, 404, "Not found");
|
||||||
|
result = 0;
|
||||||
|
|
||||||
|
done:
|
||||||
|
tor_free(url_mem);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper function for GET / or GET /tor/
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
const char *url = args->url;
|
||||||
|
{
|
||||||
const char *frontpage = get_dirportfrontpage();
|
const char *frontpage = get_dirportfrontpage();
|
||||||
|
|
||||||
if (frontpage) {
|
if (frontpage) {
|
||||||
|
size_t dlen;
|
||||||
dlen = strlen(frontpage);
|
dlen = strlen(frontpage);
|
||||||
/* Let's return a disclaimer page (users shouldn't use V1 anymore,
|
/* Let's return a disclaimer page (users shouldn't use V1 anymore,
|
||||||
and caches don't fetch '/', so this is safe). */
|
and caches don't fetch '/', so this is safe). */
|
||||||
@ -2919,12 +3020,24 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
write_http_response_header_impl(conn, dlen, "text/html", "identity",
|
write_http_response_header_impl(conn, dlen, "text/html", "identity",
|
||||||
NULL, DIRPORTFRONTPAGE_CACHE_LIFETIME);
|
NULL, DIRPORTFRONTPAGE_CACHE_LIFETIME);
|
||||||
connection_write_to_buf(frontpage, dlen, TO_CONN(conn));
|
connection_write_to_buf(frontpage, dlen, TO_CONN(conn));
|
||||||
goto done;
|
} else {
|
||||||
|
write_http_status_line(conn, 404, "Not found");
|
||||||
}
|
}
|
||||||
/* if no disclaimer file, fall through and continue */
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmpstart(url, "/tor/status-vote/current/consensus")) {
|
/** Helper function for GET /tor/status-vote/current/consensus
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
handle_get_current_consensus(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
const char *url = args->url;
|
||||||
|
const int compressed = args->compressed;
|
||||||
|
const time_t if_modified_since = args->if_modified_since;
|
||||||
|
|
||||||
|
{
|
||||||
/* v3 network status fetch. */
|
/* v3 network status fetch. */
|
||||||
smartlist_t *dir_fps = smartlist_new();
|
smartlist_t *dir_fps = smartlist_new();
|
||||||
const char *request_type = NULL;
|
const char *request_type = NULL;
|
||||||
@ -3001,7 +3114,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
dlen = dirserv_estimate_data_size(dir_fps, 0, compressed);
|
size_t dlen = dirserv_estimate_data_size(dir_fps, 0, compressed);
|
||||||
if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) {
|
if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) {
|
||||||
log_debug(LD_DIRSERV,
|
log_debug(LD_DIRSERV,
|
||||||
"Client asked for network status lists, but we've been "
|
"Client asked for network status lists, but we've been "
|
||||||
@ -3045,11 +3158,18 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmpstart(url,"/tor/status-vote/current/") ||
|
done:
|
||||||
!strcmpstart(url,"/tor/status-vote/next/")) {
|
return 0;
|
||||||
/* XXXX If-modified-since is only implemented for the current
|
}
|
||||||
* consensus: that's probably fine, since it's the only vote document
|
|
||||||
* people fetch much. */
|
/** Helper function for GET /tor/status-vote/{current,next}/...
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
const char *url = args->url;
|
||||||
|
const int compressed = args->compressed;
|
||||||
|
{
|
||||||
int current;
|
int current;
|
||||||
ssize_t body_len = 0;
|
ssize_t body_len = 0;
|
||||||
ssize_t estimated_len = 0;
|
ssize_t estimated_len = 0;
|
||||||
@ -3145,8 +3265,18 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
smartlist_free(dir_items);
|
smartlist_free(dir_items);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmpstart(url, "/tor/micro/d/")) {
|
/** Helper function for GET /tor/micro/d/...
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
const char *url = args->url;
|
||||||
|
const int compressed = args->compressed;
|
||||||
|
{
|
||||||
smartlist_t *fps = smartlist_new();
|
smartlist_t *fps = smartlist_new();
|
||||||
|
|
||||||
dir_split_resource_into_fingerprints(url+strlen("/tor/micro/d/"),
|
dir_split_resource_into_fingerprints(url+strlen("/tor/micro/d/"),
|
||||||
@ -3159,7 +3289,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
smartlist_free(fps);
|
smartlist_free(fps);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
dlen = dirserv_estimate_microdesc_size(fps, compressed);
|
size_t dlen = dirserv_estimate_microdesc_size(fps, compressed);
|
||||||
if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) {
|
if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) {
|
||||||
log_info(LD_DIRSERV,
|
log_info(LD_DIRSERV,
|
||||||
"Client asked for server descriptors, but we've been "
|
"Client asked for server descriptors, but we've been "
|
||||||
@ -3182,9 +3312,22 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper function for GET /tor/{server,extra}/...
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
const char *url = args->url;
|
||||||
|
const int compressed = args->compressed;
|
||||||
|
const or_options_t *options = get_options();
|
||||||
if (!strcmpstart(url,"/tor/server/") ||
|
if (!strcmpstart(url,"/tor/server/") ||
|
||||||
(!options->BridgeAuthoritativeDir &&
|
(!options->BridgeAuthoritativeDir &&
|
||||||
!options->BridgeRelay && !strcmpstart(url,"/tor/extra/"))) {
|
!options->BridgeRelay && !strcmpstart(url,"/tor/extra/"))) {
|
||||||
|
size_t dlen;
|
||||||
int res;
|
int res;
|
||||||
const char *msg;
|
const char *msg;
|
||||||
const char *request_type = NULL;
|
const char *request_type = NULL;
|
||||||
@ -3251,8 +3394,19 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
}
|
}
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmpstart(url,"/tor/keys/")) {
|
/** Helper function for GET /tor/keys/...
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
const char *url = args->url;
|
||||||
|
const int compressed = args->compressed;
|
||||||
|
const time_t if_modified_since = args->if_modified_since;
|
||||||
|
{
|
||||||
smartlist_t *certs = smartlist_new();
|
smartlist_t *certs = smartlist_new();
|
||||||
ssize_t len = -1;
|
ssize_t len = -1;
|
||||||
if (!strcmp(url, "/tor/keys/all")) {
|
if (!strcmp(url, "/tor/keys/all")) {
|
||||||
@ -3337,9 +3491,17 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
smartlist_free(certs);
|
smartlist_free(certs);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (connection_dir_is_encrypted(conn) &&
|
/** Helper function for GET /tor/rendezvous2/
|
||||||
!strcmpstart(url,"/tor/rendezvous2/")) {
|
*/
|
||||||
|
static int
|
||||||
|
handle_get_rendezvous2(dir_connection_t *conn, const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
const char *url = args->url;
|
||||||
|
if (connection_dir_is_encrypted(conn)) {
|
||||||
/* Handle v2 rendezvous descriptor fetch request. */
|
/* Handle v2 rendezvous descriptor fetch request. */
|
||||||
const char *descp;
|
const char *descp;
|
||||||
const char *query = url + strlen("/tor/rendezvous2/");
|
const char *query = url + strlen("/tor/rendezvous2/");
|
||||||
@ -3362,16 +3524,30 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
write_http_status_line(conn, 400, "Bad request");
|
write_http_status_line(conn, 400, "Bad request");
|
||||||
}
|
}
|
||||||
goto done;
|
goto done;
|
||||||
|
} else {
|
||||||
|
/* Not encrypted! */
|
||||||
|
write_http_status_line(conn, 404, "Not found");
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper function for GET /tor/networkstatus-bridges
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
handle_get_networkstatus_bridges(dir_connection_t *conn,
|
||||||
|
const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
const char *headers = args->headers;
|
||||||
|
|
||||||
|
const or_options_t *options = get_options();
|
||||||
if (options->BridgeAuthoritativeDir &&
|
if (options->BridgeAuthoritativeDir &&
|
||||||
options->BridgePassword_AuthDigest_ &&
|
options->BridgePassword_AuthDigest_ &&
|
||||||
connection_dir_is_encrypted(conn) &&
|
connection_dir_is_encrypted(conn)) {
|
||||||
!strcmp(url,"/tor/networkstatus-bridges")) {
|
|
||||||
char *status;
|
char *status;
|
||||||
char digest[DIGEST256_LEN];
|
char digest[DIGEST256_LEN];
|
||||||
|
|
||||||
header = http_get_header(headers, "Authorization: Basic ");
|
char *header = http_get_header(headers, "Authorization: Basic ");
|
||||||
if (header)
|
if (header)
|
||||||
crypto_digest256(digest, header, strlen(header), DIGEST_SHA256);
|
crypto_digest256(digest, header, strlen(header), DIGEST_SHA256);
|
||||||
|
|
||||||
@ -3387,75 +3563,43 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
|
|||||||
|
|
||||||
/* all happy now. send an answer. */
|
/* all happy now. send an answer. */
|
||||||
status = networkstatus_getinfo_by_purpose("bridge", time(NULL));
|
status = networkstatus_getinfo_by_purpose("bridge", time(NULL));
|
||||||
dlen = strlen(status);
|
size_t dlen = strlen(status);
|
||||||
write_http_response_header(conn, dlen, 0, 0);
|
write_http_response_header(conn, dlen, 0, 0);
|
||||||
connection_write_to_buf(status, dlen, TO_CONN(conn));
|
connection_write_to_buf(status, dlen, TO_CONN(conn));
|
||||||
tor_free(status);
|
tor_free(status);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmpstart(url,"/tor/bytes.txt")) {
|
/** Helper function for GET /tor/bytes.txt
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
handle_get_bytes(dir_connection_t *conn, const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
{
|
||||||
char *bytes = directory_dump_request_log();
|
char *bytes = directory_dump_request_log();
|
||||||
size_t len = strlen(bytes);
|
size_t len = strlen(bytes);
|
||||||
write_http_response_header(conn, len, 0, 0);
|
write_http_response_header(conn, len, 0, 0);
|
||||||
connection_write_to_buf(bytes, len, TO_CONN(conn));
|
connection_write_to_buf(bytes, len, TO_CONN(conn));
|
||||||
tor_free(bytes);
|
tor_free(bytes);
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmp(url,"/tor/robots.txt")) { /* /robots.txt will have been
|
/** Helper function for GET robots.txt or /tor/robots.txt */
|
||||||
rewritten to /tor/robots.txt */
|
static int
|
||||||
char robots[] = "User-agent: *\r\nDisallow: /\r\n";
|
handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
{
|
||||||
|
const char robots[] = "User-agent: *\r\nDisallow: /\r\n";
|
||||||
size_t len = strlen(robots);
|
size_t len = strlen(robots);
|
||||||
write_http_response_header(conn, len, 0, ROBOTS_CACHE_LIFETIME);
|
write_http_response_header(conn, len, 0, ROBOTS_CACHE_LIFETIME);
|
||||||
connection_write_to_buf(robots, len, TO_CONN(conn));
|
connection_write_to_buf(robots, len, TO_CONN(conn));
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(EXPORTMALLINFO) && defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO)
|
|
||||||
#define ADD_MALLINFO_LINE(x) do { \
|
|
||||||
smartlist_add_asprintf(lines, "%s %d\n", #x, mi.x); \
|
|
||||||
}while(0);
|
|
||||||
|
|
||||||
if (!strcmp(url,"/tor/mallinfo.txt") &&
|
|
||||||
(tor_addr_eq_ipv4h(&conn->base_.addr, 0x7f000001ul))) {
|
|
||||||
char *result;
|
|
||||||
size_t len;
|
|
||||||
struct mallinfo mi;
|
|
||||||
smartlist_t *lines;
|
|
||||||
|
|
||||||
memset(&mi, 0, sizeof(mi));
|
|
||||||
mi = mallinfo();
|
|
||||||
lines = smartlist_new();
|
|
||||||
|
|
||||||
ADD_MALLINFO_LINE(arena)
|
|
||||||
ADD_MALLINFO_LINE(ordblks)
|
|
||||||
ADD_MALLINFO_LINE(smblks)
|
|
||||||
ADD_MALLINFO_LINE(hblks)
|
|
||||||
ADD_MALLINFO_LINE(hblkhd)
|
|
||||||
ADD_MALLINFO_LINE(usmblks)
|
|
||||||
ADD_MALLINFO_LINE(fsmblks)
|
|
||||||
ADD_MALLINFO_LINE(uordblks)
|
|
||||||
ADD_MALLINFO_LINE(fordblks)
|
|
||||||
ADD_MALLINFO_LINE(keepcost)
|
|
||||||
|
|
||||||
result = smartlist_join_strings(lines, "", 0, NULL);
|
|
||||||
SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp));
|
|
||||||
smartlist_free(lines);
|
|
||||||
|
|
||||||
len = strlen(result);
|
|
||||||
write_http_response_header(conn, len, 0, 0);
|
|
||||||
connection_write_to_buf(result, len, TO_CONN(conn));
|
|
||||||
tor_free(result);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* we didn't recognize the url */
|
|
||||||
write_http_status_line(conn, 404, "Not found");
|
|
||||||
|
|
||||||
done:
|
|
||||||
tor_free(url_mem);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user