709 lines
16 KiB
C
709 lines
16 KiB
C
/*
|
|
* Copyright (C) Starfields
|
|
*/
|
|
|
|
#include <ngx_config.h>
|
|
#include <ngx_core.h>
|
|
#include <ngx_http.h>
|
|
#include "ngx_http_webdav_utils.h"
|
|
|
|
|
|
#define NGX_HTTP_DAV_NO_DEPTH -3
|
|
#define NGX_HTTP_DAV_INVALID_DEPTH -2
|
|
#define NGX_HTTP_DAV_INFINITY_DEPTH -1
|
|
|
|
|
|
static ngx_int_t ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path);
|
|
static ngx_int_t ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path);
|
|
|
|
|
|
ngx_int_t
|
|
ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found,
|
|
char *failed, u_char *path)
|
|
{
|
|
ngx_int_t rc;
|
|
ngx_uint_t level;
|
|
|
|
if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) {
|
|
level = NGX_LOG_ERR;
|
|
rc = not_found;
|
|
|
|
} else if (err == NGX_EACCES || err == NGX_EPERM) {
|
|
level = NGX_LOG_ERR;
|
|
rc = NGX_HTTP_FORBIDDEN;
|
|
|
|
} else if (err == NGX_EEXIST) {
|
|
level = NGX_LOG_ERR;
|
|
rc = NGX_HTTP_NOT_ALLOWED;
|
|
|
|
} else if (err == NGX_ENOSPC) {
|
|
level = NGX_LOG_CRIT;
|
|
rc = NGX_HTTP_INSUFFICIENT_STORAGE;
|
|
|
|
} else {
|
|
level = NGX_LOG_CRIT;
|
|
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
|
|
{
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_int_t ngx_http_dav_ext_strip_uri(ngx_http_request_t *r,
|
|
ngx_str_t *uri) {
|
|
u_char *p, *last, *host;
|
|
size_t len;
|
|
|
|
if (uri->data[0] == '/') {
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext strip uri:\"%V\" unchanged", uri);
|
|
return NGX_OK;
|
|
}
|
|
|
|
len = r->headers_in.server.len;
|
|
|
|
if (len == 0) {
|
|
goto failed;
|
|
}
|
|
|
|
#if (NGX_HTTP_SSL)
|
|
|
|
if (r->connection->ssl) {
|
|
if (ngx_strncmp(uri->data, "https://", sizeof("https://") - 1) != 0) {
|
|
goto failed;
|
|
}
|
|
|
|
host = uri->data + sizeof("https://") - 1;
|
|
|
|
} else
|
|
#endif
|
|
{
|
|
if (ngx_strncmp(uri->data, "http://", sizeof("http://") - 1) != 0) {
|
|
goto failed;
|
|
}
|
|
|
|
host = uri->data + sizeof("http://") - 1;
|
|
}
|
|
|
|
if (ngx_strncmp(host, r->headers_in.server.data, len) != 0) {
|
|
goto failed;
|
|
}
|
|
|
|
last = uri->data + uri->len;
|
|
|
|
for (p = host + len; p != last; p++) {
|
|
if (*p == '/') {
|
|
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext strip uri \"%V\" \"%*s\"", uri, last - p, p);
|
|
|
|
uri->data = p;
|
|
uri->len = last - p;
|
|
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
failed:
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext strip uri \"%V\" failed", uri);
|
|
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir)
|
|
{
|
|
char *failed;
|
|
ngx_tree_ctx_t tree;
|
|
|
|
if (dir) {
|
|
|
|
tree.init_handler = NULL;
|
|
tree.file_handler = ngx_http_dav_delete_file;
|
|
tree.pre_tree_handler = ngx_http_dav_noop;
|
|
tree.post_tree_handler = ngx_http_dav_delete_dir;
|
|
tree.spec_handler = ngx_http_dav_delete_file;
|
|
tree.data = NULL;
|
|
tree.alloc = 0;
|
|
tree.log = r->connection->log;
|
|
|
|
/* TODO: 207 */
|
|
|
|
if (ngx_walk_tree(&tree, path) != NGX_OK) {
|
|
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
if (ngx_delete_dir(path->data) != NGX_FILE_ERROR) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
failed = ngx_delete_dir_n;
|
|
|
|
} else {
|
|
|
|
if (ngx_delete_file(path->data) != NGX_FILE_ERROR) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
failed = ngx_delete_file_n;
|
|
}
|
|
|
|
return ngx_http_dav_error(r->connection->log, ngx_errno,
|
|
NGX_HTTP_NOT_FOUND, failed, path->data);
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path)
|
|
{
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
|
|
"http delete dir: \"%s\"", path->data);
|
|
|
|
if (ngx_delete_dir(path->data) == NGX_FILE_ERROR) {
|
|
|
|
/* TODO: add to 207 */
|
|
|
|
(void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_dir_n,
|
|
path->data);
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
|
|
{
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
|
|
"http delete file: \"%s\"", path->data);
|
|
|
|
if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
|
|
|
|
/* TODO: add to 207 */
|
|
|
|
(void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_file_n,
|
|
path->data);
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_int_t ensure_trailing_slash(ngx_http_request_t *r,
|
|
ngx_str_t *path) {
|
|
|
|
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
|
|
"initial path: %s", path->data);
|
|
|
|
u_char *p = ngx_pnalloc(r->pool, path->len + 1);
|
|
if (p == NULL) {
|
|
return 0;
|
|
}
|
|
u_char *last = ngx_cpystrn(p, path->data, path->len + 1);
|
|
*last++ = '/';
|
|
*last++ = '\0';
|
|
path->data = p;
|
|
path->len += 1;
|
|
|
|
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
|
|
"rewritten the path with a trailing slash: %s", path->data);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt)
|
|
{
|
|
ngx_table_elt_t *depth;
|
|
|
|
depth = r->headers_in.depth;
|
|
|
|
if (depth == NULL) {
|
|
return dflt;
|
|
}
|
|
|
|
if (depth->value.len == 1) {
|
|
|
|
if (depth->value.data[0] == '0') {
|
|
return 0;
|
|
}
|
|
|
|
if (depth->value.data[0] == '1') {
|
|
return 1;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (depth->value.len == sizeof("infinity") - 1
|
|
&& ngx_strcmp(depth->value.data, "infinity") == 0)
|
|
{
|
|
return NGX_HTTP_DAV_INFINITY_DEPTH;
|
|
}
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
|
|
"client sent invalid \"Depth\" header: \"%V\"",
|
|
&depth->value);
|
|
|
|
return NGX_HTTP_DAV_INVALID_DEPTH;
|
|
}
|
|
|
|
|
|
ngx_int_t ngx_http_dav_ext_depth(ngx_http_request_t *r,
|
|
ngx_int_t default_depth) {
|
|
ngx_table_elt_t *depth;
|
|
|
|
depth = r->headers_in.depth;
|
|
|
|
if (depth == NULL) {
|
|
return default_depth;
|
|
}
|
|
|
|
if (depth->value.len == 1) {
|
|
|
|
if (depth->value.data[0] == '0') {
|
|
return 0;
|
|
}
|
|
|
|
if (depth->value.data[0] == '1') {
|
|
return 1;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (depth->value.len == sizeof("infinity") - 1 &&
|
|
ngx_strcmp(depth->value.data, "infinity") == 0) {
|
|
return NGX_MAX_INT_T_VALUE;
|
|
}
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
|
|
"client sent invalid \"Depth\" header: \"%V\"", &depth->value);
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_http_dav_location(ngx_http_request_t *r)
|
|
{
|
|
u_char *p;
|
|
size_t len;
|
|
uintptr_t escape;
|
|
|
|
r->headers_out.location = ngx_list_push(&r->headers_out.headers);
|
|
if (r->headers_out.location == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
r->headers_out.location->hash = 1;
|
|
r->headers_out.location->next = NULL;
|
|
ngx_str_set(&r->headers_out.location->key, "Location");
|
|
|
|
escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI);
|
|
|
|
if (escape) {
|
|
len = r->uri.len + escape;
|
|
|
|
p = ngx_pnalloc(r->pool, len);
|
|
if (p == NULL) {
|
|
ngx_http_clear_location(r);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
r->headers_out.location->value.len = len;
|
|
r->headers_out.location->value.data = p;
|
|
|
|
ngx_escape_uri(p, r->uri.data, r->uri.len, NGX_ESCAPE_URI);
|
|
|
|
} else {
|
|
r->headers_out.location->value = r->uri;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
uint32_t ngx_http_dav_ext_if(ngx_http_request_t *r, ngx_str_t *uri) {
|
|
u_char *p, ch;
|
|
uint32_t token;
|
|
ngx_str_t tag;
|
|
ngx_uint_t i, n;
|
|
ngx_list_part_t *part;
|
|
ngx_table_elt_t *header;
|
|
|
|
static u_char name[] = "if";
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext if \"%V\"", uri);
|
|
|
|
part = &r->headers_in.headers.part;
|
|
header = part->elts;
|
|
|
|
for (i = 0; /* void */; i++) {
|
|
|
|
if (i >= part->nelts) {
|
|
if (part->next == NULL) {
|
|
break;
|
|
}
|
|
|
|
part = part->next;
|
|
header = part->elts;
|
|
i = 0;
|
|
}
|
|
|
|
for (n = 0; n < sizeof(name) - 1 && n < header[i].key.len; n++) {
|
|
ch = header[i].key.data[n];
|
|
|
|
if (ch >= 'A' && ch <= 'Z') {
|
|
ch |= 0x20;
|
|
}
|
|
|
|
if (name[n] != ch) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (n == sizeof(name) - 1 && n == header[i].key.len) {
|
|
p = header[i].value.data;
|
|
tag = r->uri;
|
|
|
|
while (*p != '\0') {
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext if list \"%s\"", p);
|
|
|
|
while (*p == ' ') {
|
|
p++;
|
|
}
|
|
|
|
if (*p == '<') {
|
|
tag.data = ++p;
|
|
|
|
while (*p != '\0' && *p != '>') {
|
|
p++;
|
|
}
|
|
|
|
if (*p == '\0') {
|
|
break;
|
|
}
|
|
|
|
tag.len = p++ - tag.data;
|
|
|
|
(void)ngx_http_dav_ext_strip_uri(r, &tag);
|
|
|
|
while (*p == ' ') {
|
|
p++;
|
|
}
|
|
}
|
|
|
|
if (*p != '(') {
|
|
break;
|
|
}
|
|
|
|
p++;
|
|
|
|
if (tag.len == 0 || tag.len > uri->len ||
|
|
(tag.len < uri->len && tag.data[tag.len - 1] != '/') ||
|
|
ngx_memcmp(tag.data, uri->data, tag.len)) {
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext if tag mismatch \"%V\"", &tag);
|
|
|
|
while (*p != '\0' && *p != ')') {
|
|
p++;
|
|
}
|
|
|
|
if (*p == ')') {
|
|
p++;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
while (*p != '\0') {
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext if condition \"%s\"", p);
|
|
|
|
while (*p == ' ') {
|
|
p++;
|
|
}
|
|
|
|
if (ngx_strncmp(p, "Not", 3) == 0) {
|
|
p += 3;
|
|
while (*p == ' ') {
|
|
p++;
|
|
}
|
|
goto next;
|
|
}
|
|
|
|
if (*p == '[') {
|
|
p++;
|
|
while (*p != '\0' && *p != ']') {
|
|
p++;
|
|
}
|
|
goto next;
|
|
}
|
|
|
|
if (ngx_strncmp(p, "<urn:", 5)) {
|
|
goto next;
|
|
}
|
|
|
|
p += 5;
|
|
token = 0;
|
|
|
|
for (n = 0; n < 8; n++) {
|
|
ch = *p++;
|
|
|
|
if (ch >= '0' && ch <= '9') {
|
|
token = token * 16 + (ch - '0');
|
|
continue;
|
|
}
|
|
|
|
ch = (u_char)(ch | 0x20);
|
|
|
|
if (ch >= 'a' && ch <= 'f') {
|
|
token = token * 16 + (ch - 'a' + 10);
|
|
continue;
|
|
}
|
|
|
|
goto next;
|
|
}
|
|
|
|
if (*p != '>') {
|
|
goto next;
|
|
}
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext if token: %uxD", token);
|
|
|
|
return token;
|
|
|
|
next:
|
|
|
|
while (*p != '\0' && *p != ' ' && *p != ')') {
|
|
p++;
|
|
}
|
|
|
|
if (*p == ')') {
|
|
p++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext if header mismatch");
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
ngx_http_dav_ext_node_t *
|
|
ngx_http_dav_ext_lock_lookup(ngx_http_request_t *r,
|
|
ngx_http_dav_ext_lock_t *lock, ngx_str_t *uri,
|
|
ngx_int_t depth) {
|
|
time_t now;
|
|
ngx_queue_t *q;
|
|
ngx_http_dav_ext_node_t *node;
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext lock lookup \"%V\"", uri);
|
|
|
|
if (uri->len == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
now = ngx_time();
|
|
|
|
while (!ngx_queue_empty(&lock->sh->queue)) {
|
|
q = ngx_queue_head(&lock->sh->queue);
|
|
node = (ngx_http_dav_ext_node_t *)q;
|
|
|
|
if (node->expire >= now) {
|
|
break;
|
|
}
|
|
|
|
ngx_queue_remove(q);
|
|
ngx_slab_free_locked(lock->shpool, node);
|
|
}
|
|
|
|
for (q = ngx_queue_head(&lock->sh->queue);
|
|
q != ngx_queue_sentinel(&lock->sh->queue); q = ngx_queue_next(q)) {
|
|
node = (ngx_http_dav_ext_node_t *)q;
|
|
|
|
if (uri->len >= node->len) {
|
|
if (ngx_memcmp(uri->data, node->data, node->len)) {
|
|
continue;
|
|
}
|
|
|
|
if (uri->len > node->len) {
|
|
if (node->data[node->len - 1] != '/') {
|
|
continue;
|
|
}
|
|
|
|
if (!node->infinite &&
|
|
ngx_strlchr(uri->data + node->len, uri->data + uri->len - 1, '/')) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext lock found \"%*s\"", node->len, node->data);
|
|
|
|
return node;
|
|
}
|
|
|
|
/* uri->len < node->len */
|
|
|
|
if (depth >= 0) {
|
|
if (ngx_memcmp(node->data, uri->data, uri->len)) {
|
|
continue;
|
|
}
|
|
|
|
if (uri->data[uri->len - 1] != '/') {
|
|
continue;
|
|
}
|
|
|
|
if (depth == 0 &&
|
|
ngx_strlchr(node->data + uri->len, node->data + node->len - 1, '/')) {
|
|
continue;
|
|
}
|
|
|
|
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext lock found \"%*s\"", node->len, node->data);
|
|
|
|
return node;
|
|
}
|
|
}
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
|
"http dav_ext lock not found");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
uintptr_t
|
|
ngx_http_dav_ext_format_lockdiscovery(ngx_http_request_t *r, u_char *dst,
|
|
ngx_http_dav_ext_entry_t *entry) {
|
|
size_t len;
|
|
time_t now;
|
|
|
|
if (dst == NULL) {
|
|
if (entry->lock_token == 0) {
|
|
return sizeof("<D:lockdiscovery/>\n") - 1;
|
|
}
|
|
|
|
len = sizeof("<D:lockdiscovery>\n"
|
|
"<D:activelock>\n"
|
|
"<D:locktype><D:write/></D:locktype>\n"
|
|
"<D:lockscope><D:exclusive/></D:lockscope>\n"
|
|
"<D:depth>infinity</D:depth>\n"
|
|
"<D:timeout>Second-</D:timeout>\n"
|
|
"<D:locktoken><D:href></D:href></D:locktoken>\n"
|
|
"<D:lockroot><D:href></D:href></D:lockroot>\n"
|
|
"</D:activelock>\n"
|
|
"</D:lockdiscovery>\n") -
|
|
1;
|
|
|
|
/* timeout */
|
|
len += NGX_TIME_T_LEN;
|
|
|
|
/* token */
|
|
len += ngx_http_dav_ext_format_token(NULL, entry->lock_token, 0);
|
|
|
|
/* lockroot */
|
|
len += entry->lock_root.len +
|
|
ngx_escape_html(NULL, entry->lock_root.data, entry->lock_root.len);
|
|
return len;
|
|
}
|
|
|
|
if (entry->lock_token == 0) {
|
|
dst = ngx_cpymem(dst, "<D:lockdiscovery/>\n",
|
|
sizeof("<D:lockdiscovery/>\n") - 1);
|
|
return (uintptr_t)dst;
|
|
}
|
|
|
|
now = ngx_time();
|
|
|
|
dst =
|
|
ngx_cpymem(dst, "<D:lockdiscovery>\n", sizeof("<D:lockdiscovery>\n") - 1);
|
|
|
|
dst = ngx_cpymem(dst, "<D:activelock>\n", sizeof("<D:activelock>\n") - 1);
|
|
|
|
dst = ngx_cpymem(dst, "<D:locktype><D:write/></D:locktype>\n",
|
|
sizeof("<D:locktype><D:write/></D:locktype>\n") - 1);
|
|
|
|
dst = ngx_cpymem(dst, "<D:lockscope><D:exclusive/></D:lockscope>\n",
|
|
sizeof("<D:lockscope><D:exclusive/></D:lockscope>\n") - 1);
|
|
|
|
dst = ngx_sprintf(dst, "<D:depth>%s</D:depth>\n",
|
|
entry->lock_infinite ? "infinity" : "0");
|
|
|
|
dst = ngx_sprintf(dst, "<D:timeout>Second-%T</D:timeout>\n",
|
|
entry->lock_expire - now);
|
|
|
|
dst = ngx_cpymem(dst, "<D:locktoken><D:href>",
|
|
sizeof("<D:locktoken><D:href>") - 1);
|
|
dst = (u_char *)ngx_http_dav_ext_format_token(dst, entry->lock_token, 0);
|
|
dst = ngx_cpymem(dst, "</D:href></D:locktoken>\n",
|
|
sizeof("</D:href></D:locktoken>\n") - 1);
|
|
|
|
dst = ngx_cpymem(dst, "<D:lockroot><D:href>",
|
|
sizeof("<D:lockroot><D:href>") - 1);
|
|
dst = (u_char *)ngx_escape_html(dst, entry->lock_root.data,
|
|
entry->lock_root.len);
|
|
dst = ngx_cpymem(dst, "</D:href></D:lockroot>\n",
|
|
sizeof("</D:href></D:lockroot>\n") - 1);
|
|
|
|
dst = ngx_cpymem(dst, "</D:activelock>\n", sizeof("</D:activelock>\n") - 1);
|
|
|
|
dst = ngx_cpymem(dst, "</D:lockdiscovery>\n",
|
|
sizeof("</D:lockdiscovery>\n") - 1);
|
|
|
|
return (uintptr_t)dst;
|
|
}
|
|
|
|
|
|
uintptr_t ngx_http_dav_ext_format_token(u_char *dst, uint32_t token,
|
|
ngx_uint_t brackets) {
|
|
ngx_uint_t n;
|
|
|
|
static u_char hex[] = "0123456789abcdef";
|
|
|
|
if (dst == NULL) {
|
|
return sizeof("<urn:deadbeef>") - 1 + (brackets ? 2 : 0);
|
|
}
|
|
|
|
if (brackets) {
|
|
*dst++ = '<';
|
|
}
|
|
|
|
dst = ngx_cpymem(dst, "urn:", 4);
|
|
|
|
for (n = 0; n < 4; n++) {
|
|
*dst++ = hex[token >> 28];
|
|
*dst++ = hex[(token >> 24) & 0xf];
|
|
token <<= 8;
|
|
}
|
|
|
|
if (brackets) {
|
|
*dst++ = '>';
|
|
}
|
|
|
|
return (uintptr_t)dst;
|
|
}
|