Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
302 changes: 268 additions & 34 deletions packages/php-wasm/compile/php-wasm-dns-polyfill/dns_polyfill.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include "zend_API.h"
#include "dns_polyfill.h"
#include "ext/standard/info.h"
#include <emscripten.h>
#include <stdlib.h>
#include <string.h>

/* ZEND_FE_END was added in PHP 5.3.7; provide fallback for PHP 5.2 */
#ifndef ZEND_FE_END
Expand Down Expand Up @@ -124,21 +127,200 @@ typedef union {
uint8_t qb2[65536];
} querybuf;

/* Node returns tab-separated, hex-encoded fields. Browsers have no resolver
* syscall, so the bridge returns NULL and the public functions fail normally. */
#ifdef PLAYGROUND_JSPI
EM_ASYNC_JS(char *, wasm_dns_resolve, (const char *host, const char *type), {
if (typeof Module['userSpace'] === 'undefined' || !Module['userSpace'].dnsResolve) return 0;
const value = await Module['userSpace'].dnsResolve(UTF8ToString(host), UTF8ToString(type));
const size = lengthBytesUTF8(value) + 1;
const result = _malloc(size);
stringToUTF8(value, result, size);
return result;
});
#else
EM_JS(char *, wasm_dns_resolve, (const char *host, const char *type), {
if (typeof Module['userSpace'] === 'undefined' || !Module['userSpace'].dnsResolve) return 0;
return Asyncify.handleSleep((wakeUp) => {
Module['userSpace'].dnsResolve(UTF8ToString(host), UTF8ToString(type))
.then((value) => {
const size = lengthBytesUTF8(value) + 1;
const result = _malloc(size);
stringToUTF8(value, result, size);
wakeUp(result);
})
.catch(() => wakeUp(0));
});
});
#endif

static char *dns_field(char **cursor)
{
char *value = *cursor, *delimiter;
if (!value) return NULL;
delimiter = strchr(value, '\t');
if (delimiter) { *delimiter = '\0'; *cursor = delimiter + 1; }
else *cursor = NULL;
return value;
}

static char *dns_decode(const char *encoded)
{
size_t i, length;
char *decoded;
if (!encoded || strlen(encoded) % 2) return NULL;
length = strlen(encoded) / 2;
decoded = emalloc(length + 1);
for (i = 0; i < length; i++) {
char byte[3] = { encoded[i * 2], encoded[i * 2 + 1], '\0' };
decoded[i] = (char) strtol(byte, NULL, 16);
}
decoded[length] = '\0';
return decoded;
}

/* PHP 5 copies strings only when the duplicate flag is supplied. */
static void dns_assoc_string(zval *record, const char *key, char *value)
{
#if PHP_MAJOR_VERSION >= 7
add_assoc_string(record, key, value);
#else
add_assoc_string(record, (char *) key, value, 1);
#endif
}

static void dns_append_string(zval *array, char *value)
{
#if PHP_MAJOR_VERSION >= 7
add_next_index_string(array, value);
#else
add_next_index_string(array, value, 1);
#endif
}

static void dns_add_string(zval *record, const char *key, const char *encoded)
{
char *value = dns_decode(encoded);
if (value) { dns_assoc_string(record, key, value); efree(value); }
}

static void dns_add_number(zval *record, const char *key, const char *encoded)
{
char *value = dns_decode(encoded);
if (value) { add_assoc_long(record, (char *) key, strtol(value, NULL, 10)); efree(value); }
}

static void dns_add_records(zval *records, const char *host, char *response)
{
char *line = response;
while (line && *line) {
char *next = strchr(line, '\n'), *cursor, *type;
if (next) *next = '\0';
cursor = line;
type = dns_field(&cursor);
if (type && cursor) {
#if PHP_MAJOR_VERSION >= 7
zval record_value;
zval *record = &record_value;
#else
zval *record;
MAKE_STD_ZVAL(record);
#endif
array_init(record);
dns_assoc_string(record, "host", (char *) host);
dns_assoc_string(record, "class", "IN");
add_assoc_long(record, "ttl", 0);
dns_assoc_string(record, "type", type);
if (!strcmp(type, "A")) dns_add_string(record, "ip", dns_field(&cursor));
else if (!strcmp(type, "AAAA")) dns_add_string(record, "ipv6", dns_field(&cursor));
else if (!strcmp(type, "CNAME") || !strcmp(type, "NS") || !strcmp(type, "PTR")) dns_add_string(record, "target", dns_field(&cursor));
else if (!strcmp(type, "MX")) { dns_add_number(record, "pri", dns_field(&cursor)); dns_add_string(record, "target", dns_field(&cursor)); }
else if (!strcmp(type, "SRV")) { dns_add_number(record, "pri", dns_field(&cursor)); dns_add_number(record, "weight", dns_field(&cursor)); dns_add_number(record, "port", dns_field(&cursor)); dns_add_string(record, "target", dns_field(&cursor)); }
else if (!strcmp(type, "CAA")) { dns_add_number(record, "flags", dns_field(&cursor)); dns_add_string(record, "tag", dns_field(&cursor)); dns_add_string(record, "value", dns_field(&cursor)); }
else if (!strcmp(type, "TXT")) {
#if PHP_MAJOR_VERSION >= 7
zval entries_value;
zval *entries = &entries_value;
#else
zval *entries;
MAKE_STD_ZVAL(entries);
#endif
dns_add_string(record, "txt", dns_field(&cursor));
array_init(entries);
while (cursor) { char *entry = dns_decode(dns_field(&cursor)); if (entry) { dns_append_string(entries, entry); efree(entry); } }
add_assoc_zval(record, "entries", entries);
}
else if (!strcmp(type, "SOA")) {
const char *keys[] = { "mname", "rname", "serial", "refresh", "retry", "expire", "minimum" }; int i;
for (i = 0; i < 7; i++) { if (i < 2) dns_add_string(record, keys[i], dns_field(&cursor)); else dns_add_number(record, keys[i], dns_field(&cursor)); }
}
else if (!strcmp(type, "NAPTR")) {
const char *keys[] = { "order", "pref", "flags", "services", "regex", "replacement" }; int i;
for (i = 0; i < 6; i++) { if (i < 2) dns_add_number(record, keys[i], dns_field(&cursor)); else dns_add_string(record, keys[i], dns_field(&cursor)); }
}
add_next_index_zval(records, record);
}
line = next ? next + 1 : NULL;
}
}

static int dns_resolve(zval *records, const char *host, const char *type)
{
char *response = wasm_dns_resolve(host, type);
if (!response) return 0;
dns_add_records(records, host, response);
free(response);
return 1;
}

static const char *dns_type(const char *type)
{
struct dns_name { const char *name; } types[] = {
{ "A" }, { "AAAA" }, { "CAA" }, { "CNAME" }, { "MX" },
{ "NAPTR" }, { "NS" }, { "PTR" }, { "SOA" }, { "SRV" }, { "TXT" }
};
int i;
for (i = 0; i < (int) (sizeof(types) / sizeof(types[0])); i++) {
if (!strcasecmp(type, types[i].name)) return types[i].name;
}
return NULL;
}

static void dns_resolve_mask(zval *records, const char *host, long mask)
{
const long supported = PHP_DNS_A | PHP_DNS_NS | PHP_DNS_CNAME | PHP_DNS_SOA |
PHP_DNS_PTR | PHP_DNS_CAA | PHP_DNS_MX | PHP_DNS_TXT | PHP_DNS_SRV |
PHP_DNS_NAPTR | PHP_DNS_AAAA;
struct dns_name { long mask; const char *name; } types[] = {
{ PHP_DNS_A, "A" }, { PHP_DNS_NS, "NS" }, { PHP_DNS_CNAME, "CNAME" }, { PHP_DNS_SOA, "SOA" }, { PHP_DNS_PTR, "PTR" }, { PHP_DNS_CAA, "CAA" }, { PHP_DNS_MX, "MX" }, { PHP_DNS_TXT, "TXT" }, { PHP_DNS_SRV, "SRV" }, { PHP_DNS_NAPTR, "NAPTR" }, { PHP_DNS_AAAA, "AAAA" }
};
int i;
if (mask == PHP_DNS_ANY || mask == PHP_DNS_ALL) mask = supported;
else if (mask & ~supported) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Some requested DNS record types are not supported in PHP.wasm.");
mask &= supported;
}
for (i = 0; i < (int) (sizeof(types) / sizeof(types[0])); i++) if (mask & types[i].mask) dns_resolve(records, host, types[i].name);
}

PHP_FUNCTION(dns_check_record)
{
HEADER *hp;
querybuf answer = {0};
char *hostname;
#if PHP_MAJOR_VERSION >= 7
size_t hostname_len;
size_t rectype_len = 0;
zend_string *rectype = NULL;
char *rectype = NULL;
#else
int hostname_len;
int rectype_len = 0;
char *rectype = NULL;
#endif
int type = DNS_T_MX, i;
const char *type;
#if PHP_MAJOR_VERSION >= 7
zval records;
#else
zval *records;
#endif

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &hostname, &hostname_len, &rectype, &rectype_len) == FAILURE) {
return;
Expand All @@ -149,9 +331,31 @@ PHP_FUNCTION(dns_check_record)
RETURN_FALSE;
}

php_error_docref(NULL TSRMLS_CC, E_WARNING, "dns_check_record() always returns false in PHP.wasm.");
type = rectype ? dns_type(rectype) : "MX";
if (!type) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid DNS record type");
RETURN_FALSE;
}

RETURN_FALSE;
#if PHP_MAJOR_VERSION >= 7
array_init(&records);
dns_resolve(&records, hostname, type);
if (zend_hash_num_elements(Z_ARRVAL(records))) {
zval_ptr_dtor(&records);
RETURN_TRUE;
}
zval_ptr_dtor(&records);
#else
MAKE_STD_ZVAL(records);
array_init(records);
dns_resolve(records, hostname, type);
if (zend_hash_num_elements(Z_ARRVAL_P(records))) {
zval_ptr_dtor(&records);
RETURN_TRUE;
}
zval_ptr_dtor(&records);
#endif
RETURN_FALSE;
}

/* {{{ Get any Resource Record corresponding to a given Internet host name */
Expand All @@ -167,13 +371,6 @@ PHP_FUNCTION(dns_get_record)
long type_param = PHP_DNS_ANY;
#endif
zval *authns = NULL, *addtl = NULL;
int type_to_fetch;
int dns_errno;
HEADER *hp;
querybuf answer = {0};
uint8_t *cp = NULL, *end = NULL;
int n, qd, an, ns = 0, ar = 0;
int type, first_query = 1, store_results = 1;
zend_bool raw = 0;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|lz!z!b",
Expand All @@ -194,10 +391,13 @@ PHP_FUNCTION(dns_get_record)
}
}

php_error_docref(NULL TSRMLS_CC, E_WARNING, "dns_get_record() always returns an empty array in PHP.wasm.");

/* Initialize the return array */
array_init(return_value);
if (!hostname_len) return;
if (raw) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Raw DNS response mode is not supported in PHP.wasm.");
return;
}
dns_resolve_mask(return_value, hostname, type_param);
}

/* }}} */
Expand All @@ -213,13 +413,11 @@ PHP_FUNCTION(dns_get_mx)
int hostname_len;
#endif
zval *mx_list, *weight_list = NULL;
int count, qdc;
u_short type, weight;
querybuf answer = {0};
char buf[MAXHOSTNAMELEN] = {0};
HEADER *hp;
uint8_t *cp, *end;
int i;
#if PHP_MAJOR_VERSION >= 7
zval records;
#else
zval *records;
#endif

#if PHP_MAJOR_VERSION >= 7
ZEND_PARSE_PARAMETERS_START(2, 3)
Expand All @@ -234,21 +432,57 @@ PHP_FUNCTION(dns_get_mx)
}
#endif

array_init(mx_list);
if (!mx_list) {
RETURN_FALSE;
#if PHP_MAJOR_VERSION >= 7
mx_list = zend_try_array_init(mx_list);
if (!mx_list) return;
if (weight_list) {
weight_list = zend_try_array_init(weight_list);
if (!weight_list) return;
}
#else
array_init(mx_list);
if (weight_list) array_init(weight_list);
#endif

if (weight_list) {
array_init(weight_list);
if (!weight_list) {
RETURN_FALSE;
if (!hostname_len) RETURN_FALSE;
#if PHP_MAJOR_VERSION >= 7
array_init(&records);
dns_resolve(&records, hostname, "MX");
if (!zend_hash_num_elements(Z_ARRVAL(records))) {
zval_ptr_dtor(&records);
RETURN_FALSE;
}
#else
MAKE_STD_ZVAL(records);
array_init(records);
dns_resolve(records, hostname, "MX");
if (!zend_hash_num_elements(Z_ARRVAL_P(records))) {
zval_ptr_dtor(&records);
RETURN_FALSE;
}
#endif
#if PHP_MAJOR_VERSION >= 7
{
zval *record;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL(records), record) {
zval *target = zend_hash_str_find(Z_ARRVAL_P(record), "target", sizeof("target") - 1);
zval *priority = zend_hash_str_find(Z_ARRVAL_P(record), "pri", sizeof("pri") - 1);
if (target) dns_append_string(mx_list, Z_STRVAL_P(target));
if (weight_list && priority) add_next_index_long(weight_list, Z_LVAL_P(priority));
} ZEND_HASH_FOREACH_END();
}
#else
{
HashPosition position;
zval **record, **target, **priority;
for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(records), &position); zend_hash_get_current_data_ex(Z_ARRVAL_P(records), (void **) &record, &position) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_P(records), &position)) {
if (zend_hash_find(Z_ARRVAL_PP(record), "target", sizeof("target"), (void **) &target) == SUCCESS) dns_append_string(mx_list, Z_STRVAL_PP(target));
if (weight_list && zend_hash_find(Z_ARRVAL_PP(record), "pri", sizeof("pri"), (void **) &priority) == SUCCESS) add_next_index_long(weight_list, Z_LVAL_PP(priority));
}
}

php_error_docref(NULL TSRMLS_CC, E_WARNING, "dns_get_mx() always returns an empty array in PHP.wasm.");

RETURN_FALSE;
#endif
zval_ptr_dtor(&records);
RETURN_TRUE;
}
/* }}} */

Expand Down
Loading
Loading