Beispiel #1
0
/*
 * Create the next authentication request to send to server if authentication
 * is not completed. If it is completed, attachs the 'Authorization' header
 * to the original request.
 */
static NTSTATUS http_create_auth_request(TALLOC_CTX *mem_ctx,
					 struct gensec_security *gensec_ctx,
					 struct tevent_context *ev,
					 enum http_auth_method auth,
					 struct http_request *original_request,
					 struct http_request *auth_response,
					 struct http_request **auth_request)
{
	NTSTATUS status;
	DATA_BLOB in, out;

	if (auth_response) {
		status = http_parse_auth_response(auth, auth_response, &in);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	} else {
		in = data_blob_null;
	}

	status = gensec_update_ev(gensec_ctx, mem_ctx, ev, in, &out);
	if (NT_STATUS_IS_OK(status)) {
		if (out.length) {
			http_add_header(original_request,
					&original_request->headers,
					"Authorization", (char*)out.data);
		}
	}

	if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
		NTSTATUS status2;

		*auth_request = talloc_zero(mem_ctx, struct http_request);
		if (*auth_request == NULL) {
			return NT_STATUS_NO_MEMORY;
		}

		status2 = http_copy_header(original_request, *auth_request);
		if (!NT_STATUS_IS_OK(status2)) {
			talloc_free(*auth_request);
			return status2;
		}

		http_replace_header(*auth_request, &((*auth_request)->headers),
				    "Content-Length", "0");
		if (out.length) {
			http_add_header(*auth_request,
					&((*auth_request)->headers),
					"Authorization", (char*)out.data);
		}
	}

	return status;
}
Beispiel #2
0
static inline ssize_t
http_method (int fd, Http_destination *dest,
	     Http_method method, ssize_t length)
{
  char str[1024]; /* FIXME: possible buffer overflow */
  Http_request *request;
  ssize_t n;

  if (fd == -1)
    {
      log_error ("http_method: fd == -1");
      return -1;
    }

  n = 0;
  if (dest->proxy_name != NULL)
    n = sprintf (str, "http://%s:%d", dest->host_name, dest->host_port);
  sprintf (str + n, "/index.html?crap=%ld", time (NULL));

  request = http_create_request (method, str, 1, 1);
  if (request == NULL)
    return -1;

  sprintf (str, "%s:%d", dest->host_name, dest->host_port);
  http_add_header (&request->header, "Host", str);

  if (length >= 0)
    {
      sprintf (str, "%d", length);
      http_add_header (&request->header, "Content-Length", str);
    }

  http_add_header (&request->header, "Connection", "close");

  if (dest->proxy_authorization)
    {
      http_add_header (&request->header,
		       "Proxy-Authorization",
		       dest->proxy_authorization);
    }

  if (dest->user_agent)
    {
      http_add_header (&request->header,
		       "User-Agent",
		       dest->user_agent);
    }

  n = http_write_request (fd, request);
  http_destroy_request (request);
  return n;
}
Beispiel #3
0
static unsigned int
vstat_reply(struct http_request *request, const char *arg, void *data)
{
	struct vstat_priv_t *vstat;
	struct agent_core_t *core = data;
	struct http_response *resp;

	(void)arg;
	GET_PRIV(core, vstat);

	if (check_reopen(&vstat->http)) {
		http_reply(request->connection, 500, "Couldn't open shmlog");
		return 0;
	}

	do_json(vstat->http.vd, vstat->http.vsb);

	resp = http_mkresp(request->connection, 200, NULL);
	resp->data = VSB_data(vstat->http.vsb);
	resp->ndata = VSB_len(vstat->http.vsb);
	http_add_header(resp,"Content-Type","application/json");
	send_response(resp);
	http_free_resp(resp);
	VSB_clear(vstat->http.vsb);
	return 0;
}
Beispiel #4
0
int
http_send_html(http_t *ctx, const char *data, ssize_t size)
{
  http_add_header(ctx, "Content-Type: text/html");
  http_send_document(ctx, data, size);
  return 0;
}
Beispiel #5
0
static int
http_parse_header(nni_list *hdrs, void *line)
{
	char *key = line;
	char *val;
	char *end;

	// Find separation between key and value
	if ((val = strchr(key, ':')) == NULL) {
		return (NNG_EPROTO);
	}

	// Trim leading and trailing whitespace from header
	*val = '\0';
	val++;
	while (*val == ' ' || *val == '\t') {
		val++;
	}
	end = val + strlen(val);
	end--;
	while ((end > val) && (*end == ' ' || *end == '\t')) {
		*end = '\0';
		end--;
	}

	return (http_add_header(hdrs, key, val));
}
Beispiel #6
0
void http_printf_header ( http_request *h, gchar *key, gchar *fmt, ... ) {
    gchar *val;
    va_list data;
    va_start( data, fmt );
    val = g_strdup_vprintf( fmt, data );
    http_add_header( h, key, val );
    va_end( data );
    g_free( val );
}
Beispiel #7
0
static int send_auth_response(struct MHD_Connection *connection)
{
	struct http_response *resp = http_mkresp(connection, 401, "Authorize, please.\n\n" \
	    "If Varnish Agent was installed from packages, the /etc/varnish/agent_secret " \
	    "file contains generated credentials.");
	http_add_header(resp, "WWW-Authenticate", "Basic realm=varnish-agent");
	send_response2(resp);
	http_free_resp(resp);
	return 1;
}
Beispiel #8
0
/**
 * 仅解析查询部分,会进行编码转换
 * 返回0=成功
 */
int http_parse_query_str(const char *str, keyvalq *headers) {
  char *line=NULL;
  char *argument;
  char *p;
  const char *query_part;
  int result = -1;

  TAILQ_INIT(headers);

  query_part = str;

  /* No arguments - we are done */
  if (!query_part || !strlen(query_part)) {
    result = 0;
    goto done;
  }

  if ((line = strdup(query_part)) == NULL) {
    goto error;
  }

  p = argument = line;
  while (p != NULL && *p != '\0') {
    char *key, *value, *decoded_value;
    argument = strsep(&p, "&");

    value = argument;
    key = strsep(&value, "=");
    if (value == NULL || *key == '\0') {
      goto error;
    }

    decoded_value = http_decode_uri(value, 1, NULL);
    if ( decoded_value == NULL ) {
      goto error;
    }
    http_add_header(headers, key, decoded_value);
    free(decoded_value);
  }

  result = 0;
  goto done;

error:
  http_clear_headers(headers);

done:
  if (line) free(line);

  return result;
}
Beispiel #9
0
int kvresponse_send(kvresponse_t *kvres, int sockfd) {
  int code = http_code_for_response_type(kvres->type);
  if (code < 0) return -1;

  struct http_outbound *msg = http_start_response(sockfd, code);
  if (!msg) return -1;

  char lenbuf[10] = "0";
  if (kvres->body)
    sprintf(lenbuf, "%ld", strlen(kvres->body));
  http_add_header(msg, "Content-Length", lenbuf);
  http_end_headers(msg);
  http_add_string(msg, kvres->body);

  return http_send_and_free(msg);
}
Beispiel #10
0
void
http_set_content_type(struct http_response *resp, const char *path)
{
	struct http_content_type *cp;
	char *ext;

	ext = strrchr(path, '.');
	if (ext) {
		for (cp = http_content_types; cp->file_ext; cp++) {
			if (!strcmp(ext, cp->file_ext)) {
				http_add_header(resp, "Content-Type",
				    cp->content_type);
				break;
			}
		}
	}
}
Beispiel #11
0
int http_serve_file ( http_request *h, const gchar *docroot ) {
    gchar *path;
    guint fd, status;

    path = http_fix_path( h->uri, docroot );
    fd   = http_open_file( path, &status );

    http_add_header(  h, "Content-Type", http_mime_type( path ) );
    http_send_header( h, status, fd == -1 ? "Not OK" : "OK" );

    if ( fd != -1 )
	http_sendfile( h, fd );

    close(fd);
    g_free(path);
    return ( fd != -1 );
}
Beispiel #12
0
/**
 * Copy the request headers from src to dst
 */
static NTSTATUS http_copy_header(struct http_request *src,
				 struct http_request *dst)
{
	struct http_header *h;

	dst->type = src->type;
	dst->major = src->major;
	dst->minor = src->minor;
	dst->uri = talloc_strdup(dst, src->uri);

	for (h = src->headers; h != NULL; h = h->next) {
		http_add_header(dst, &dst->headers, h->key, h->value);
	}
	dst->headers_size = src->headers_size;

	return NT_STATUS_OK;
}
Beispiel #13
0
static void param_json(struct http_request *request, struct vparams_priv_t *vparams)
{
	struct ipc_ret_t vret;
	char *tmp;
	ipc_run(vparams->vadmin, &vret, "param.show -l");
	if (vret.status == 200) {
		tmp = vparams_show_json(vret.answer);
		struct http_response *resp = http_mkresp(request->connection, 200, tmp);
		http_add_header(resp,"Content-Type","application/json");
		send_response(resp);
		free(tmp);
		http_free_resp(resp);
	} else {
		http_reply(request->connection, 500, vret.answer);
	}
	free(vret.answer);
}
Beispiel #14
0
int http_serve_template ( http_request *h, gchar *file, GHashTable *data ) {
    gchar *form;
    guint r, n;

    form = parse_template( file, data );
    n = strlen(form);

    http_add_header( h, "Content-Type", "text/html" );
    http_send_header( h, 200, "OK" );

    r = g_io_channel_write( h->sock, form, n, &n );

    g_free( form );

    if ( r != G_IO_ERROR_NONE ) {
	g_warning( "Serving template to %s failed: %m", h->peer_ip );
	return 0;
    }

    return 1;
}
Beispiel #15
0
static void backends_json(struct http_request *request,
    struct vbackends_priv_t *vbackends)
{
	struct vsb *json;
	struct ipc_ret_t vret;
	ipc_run(vbackends->vadmin, &vret, "backend.list");
	if (vret.status == 200) {
		json = VSB_new_auto();
		assert(json);
		vbackends_show_json(json, vret.answer);
		AZ(VSB_finish(json));
		struct http_response *resp = http_mkresp(request->connection,
		    200, VSB_data(json));
		http_add_header(resp,"Content-Type","application/json");
		send_response(resp);
		VSB_delete(json);
		http_free_resp(resp);
	} else
		http_reply(request->connection, 500, vret.answer);
	free(vret.answer);
}
Beispiel #16
0
static unsigned int
vcl_json(struct http_request *request, const char *arg, void *data)
{
	struct agent_core_t *core = data;
	struct vcl_priv_t *vcl;
	struct ipc_ret_t vret;
	struct vsb *json;
	struct http_response *resp;

	GET_PRIV(core, vcl);

	assert(STARTS_WITH(request->url, "/vcljson"));
	assert(request->method == M_GET);

	if (arg) {
		http_reply(request->connection, 404,
		    "/vcljson takes no argument");
		return (0);
	}

	ipc_run(vcl->vadmin, &vret, "vcl.list");
	if (vret.status == 400)
		http_reply(request->connection, 500, vret.answer);
	else {
		json = vcl_list_json(vret.answer);
		assert(VSB_finish(json) == 0);
		resp = http_mkresp(request->connection, 200, NULL);
		resp->data = VSB_data(json);
		resp->ndata = VSB_len(json);
		http_add_header(resp, "Content-Type", "application/json");
		send_response(resp);
		http_free_resp(resp);
		VSB_clear(json);
		VSB_delete(json);
	}
	free(vret.answer);
	return (0);
}
Beispiel #17
0
static unsigned int vlog_reply(struct http_request *request, void *data)
{
	struct vlog_req_priv vrp = { .limit = 10 };
	int disp_status;
	char *p;
	char *tag = NULL;
	char *tag_re = NULL;
	struct VSL_data *vsl = NULL;
	struct VSLQ *vslq = NULL;
	struct VSL_cursor *c = NULL;
	enum VSL_grouping_e grouping = VSL_g_request;
	struct agent_core_t *core = data;

	p = next_slash(request->url + 1);
	if (p) {
		char *lim = strdup(p);
		assert(lim);
		char *tmp2 = strchr(lim, '/');
		if (tmp2 && *tmp2) *tmp2 = '\0';

		int j = sscanf(lim, "%u", &vrp.limit);
		if(j != 1) {
			free(lim);
			http_reply(request->connection, 500, "Not a number");
			return 0;
		}

		free(lim);
		p = next_slash(p);
	}

	if (p) {
		tag = strdup(p);
		char *tmp2 = strchr(tag,'/');
		if (tmp2 && *tmp2) *tmp2 = '\0';
		p = next_slash(p);
	}

	if (p) {
		tag_re = strdup(p);
		char *tmp2 = strchr(tag_re, '/');
		if (tmp2 && *tmp2) *tmp2 = '\0';
		p = next_slash(p);
	}
	
	vrp.answer = VSB_new_auto();
	assert(vrp.answer != NULL);

	vrp.vsm = VSM_New();
	assert(vrp.vsm);
	if (!VSM_n_Arg(vrp.vsm, core->config->n_arg)) {
		VSB_printf(vrp.answer, "Error in creating shmlog: %s",
		    VSM_Error(vrp.vsm));
		VSB_finish(vrp.answer);
		http_reply(request->connection, 500, VSB_data(vrp.answer));
		goto cleanup;
	}

	if (VSM_Open(vrp.vsm) != 0) {
		VSB_printf(vrp.answer, "Error in opening shmlog: %s",
		    VSM_Error(vrp.vsm));
		VSB_finish(vrp.answer);
		http_reply(request->connection, 500, VSB_data(vrp.answer));
		goto cleanup;
	}
	
	vsl = VSL_New();
	assert(vsl);

	if (tag) {
		grouping = VSL_g_raw;
		
		if (VSL_Arg(vsl, 'i', tag) < 0) {
			VSB_printf(vrp.answer, "Unable to specify tag '%s': %s",
			    tag, VSL_Error(vsl));
			VSB_finish(vrp.answer);
			http_reply(request->connection, 500, VSB_data(vrp.answer));
			goto cleanup;
		}
		if (tag_re) {
			VSL_Arg(vsl,'I', tag_re);
		}
	}

	c = VSL_CursorVSM(vsl, vrp.vsm,
	    VSL_COPT_BATCH | VSL_COPT_TAILSTOP);
	if (c == NULL) {
		VSB_printf(vrp.answer, "Can't open log (%s)",
		    VSL_Error(vsl));
		VSB_finish(vrp.answer);
		http_reply(request->connection, 500, VSB_data(vrp.answer));
		goto cleanup;
	}

	
	vslq = VSLQ_New(vsl, &c, grouping, NULL);
	if (vslq == NULL) {
		VSB_clear(vrp.answer);
		VSB_printf(vrp.answer, "Error in creating query: %s",
		    VSL_Error(vsl));
		http_reply(request->connection, 500, VSB_data(vrp.answer));
		goto cleanup;
	}

	VSB_printf(vrp.answer, "{ \"log\": [");

	do {
		disp_status = VSLQ_Dispatch(vslq, vlog_cb_func, &vrp);
	} while (disp_status == 1 && vrp.entries < vrp.limit);

	VSB_printf(vrp.answer, "\n] }\n");

	assert(VSB_finish(vrp.answer) == 0);
	if (VSB_len(vrp.answer) > 1) {
		struct http_response *resp = http_mkresp(request->connection, 200, NULL);
		resp->data = VSB_data(vrp.answer);
		resp->ndata = VSB_len(vrp.answer);
		http_add_header(resp,"Content-Type","application/json");
		send_response(resp);
		http_free_resp(resp);
	} else {
		http_reply(request->connection, 500, "FAIL");
	}

 cleanup:
	free(tag);
	free(tag_re);
	VSB_delete(vrp.answer);
	if (vslq)
		VSLQ_Delete(&vslq);
	if (vsl)
		VSL_Delete(vsl);
	if (vrp.vsm)
		VSM_Delete(vrp.vsm);
	vrp.answer = NULL;
	return 0;
}

void vlog_init(struct agent_core_t *core)
{
	struct agent_plugin_t *plug;
	struct vlog_priv_t *priv;

	ALLOC_OBJ(priv);
	plug = plugin_find(core,"vlog");
	plug->data = priv;

	http_register_url(core, "/log", M_GET, vlog_reply, core);
}
Beispiel #18
0
static unsigned int vlog_reply(struct http_request *request, void *data)
{
	struct vlog_priv_t *vlog;
	int ret;
	char *limit = NULL;
	char *p;
	char *tag = NULL;
	char *itag = NULL;
	struct agent_core_t *core = data;
	GET_PRIV(data,vlog);
	p = next_slash(request->url + 1);

	assert(vlog->tag==NULL);
	assert(vlog->answer == NULL);

	if (p) {
		limit = strdup(p);
		assert(limit);
		char *tmp2 = index(limit,'/');
		if (tmp2 && *tmp2) *tmp2 = '\0';

		if(!(atoi(limit) > 0)) {
			free(limit);
			send_response_fail(request->connection,"Not a number");
			return 0;
		}
		p = next_slash(p);
	}
	if (p) {
		tag = strdup(p);
		char *tmp2 = index(tag,'/');
		if (tmp2 && *tmp2) *tmp2 = '\0';
		p = next_slash(p);
	}
	if (p) {
		itag = strdup(p);
		char *tmp2 = index(itag,'/');
		if (tmp2 && *tmp2) *tmp2 = '\0';
		p = next_slash(p);
	}
	vlog->answer = VSB_new_auto();
	assert(vlog->answer != NULL);
	vlog->vd = VSM_New();
	assert(VSL_Arg(vlog->vd, 'n', core->config->n_arg));
	VSL_Setup(vlog->vd);
	VSL_Arg(vlog->vd, 'd', "");
	if (tag) {
		VSL_Arg(vlog->vd, 'i', tag);
		if (itag)
			VSL_Arg(vlog->vd,'I',itag);
	} else {
		VSL_Arg(vlog->vd, 'k', limit ? limit : "10");
	}

	if (limit)
		free(limit);
	VSB_printf(vlog->answer, "{ \"log\": [");
	ret = VSL_Open(vlog->vd, 1);
	if (ret) {
		send_response_fail(request->connection, "Error in opening shmlog");
		goto cleanup;
	}

	if (tag == NULL) {
		do_order(vlog);
	} else {
		do_unorder(vlog);
	}

	VSB_printf(vlog->answer, "\n] }\n");
	assert(VSB_finish(vlog->answer) == 0);
	if (VSB_len(vlog->answer) > 1) {
		struct http_response *resp = http_mkresp(request->connection, 200, NULL);
		resp->data = VSB_data(vlog->answer);
		resp->ndata = VSB_len(vlog->answer);
		http_add_header(resp,"Content-Type","application/json");
		send_response2(resp);
		http_free_resp(resp);
	} else {
		send_response_fail(request->connection, "FAIL");
	}

 cleanup:
	free(tag);
	free(itag);
	VSB_clear(vlog->answer);
	VSB_delete(vlog->answer);
	VSM_Delete(vlog->vd);
	vlog->answer = NULL;
	vlog->entries = 0;
	return 0;
}
Beispiel #19
0
void http_send_redirect( http_request *h, gchar *dest ) {
    http_add_header ( h, "Location", dest );
    http_send_header( h, 302, "Moved" );
}
Beispiel #20
0
int
nni_http_res_add_header(nni_http_res *res, const char *key, const char *val)
{
	return (http_add_header(&res->hdrs, key, val));
}
Beispiel #21
0
int
nni_http_req_add_header(nni_http_req *req, const char *key, const char *val)
{
	return (http_add_header(&req->hdrs, key, val));
}
Beispiel #22
0
static unsigned int
vcl_reply(struct http_request *request, void *data)
{
	struct agent_core_t *core = data;
	struct vcl_priv_t *vcl;
	struct http_response *resp;
	struct ipc_ret_t vret;
	char id[ID_LEN + 1];
	int ret;
	int status;

	GET_PRIV(core, vcl);

	if (request->method == M_GET) {
		if (!strcmp(request->url, "/vclactive") || !strcmp(request->url,"/vclactive/")) {
			/*
			 * vcl.list output:
			 *
			 * V3/4 : (active|available|discarded) (refcnt) (name)
			 * V4.1 : (active|available|discarded) (state) \
			 *            (busycnt|) (name)
			 */
			ipc_run(vcl->vadmin,&vret,"vcl.list");
			if (vret.status == 400) {
				http_reply(request->connection, 500, vret.answer);
			} else {
				char **tp, *tok[5];
				char *p, *last;
				char *line;

				memset(tok, '\0', sizeof(tok));
				for (p = vret.answer, last = NULL;
				    (line = strtok_r(p, "\n", &last));
				    p = NULL) {
					if (strncmp("active", line, 6))
						continue;
					last = NULL;
					for (p = line, tp = tok;
					    tp < &tok[4] &&
					    (*tp = strtok_r(p, " ", &last));
					    p = NULL) {
						if (**tp != '\0')
							tp++;
                                        }
				}
				if (!tok[2] || !tok[3]) {
					http_reply(request->connection,
					    500, "No active VCL");
				} else {
					strcpy(vret.answer,
					    tok[3] ? tok[3] : tok[2]);
					http_reply(request->connection,
					    200, vret.answer);
				}
			}
			free(vret.answer);
			return 0;
		} else if (!strcmp(request->url, "/vcl") || !strcmp(request->url,"/vcl/")) {
			ipc_run(vcl->vadmin, &vret, "vcl.list");
			if (vret.status == 400) {
				http_reply(request->connection, 500, vret.answer);
			} else {
				http_reply(request->connection, 200, vret.answer);
			}
			free(vret.answer);
			return 0;
		} else if (!strncmp(request->url,"/vcl/",strlen("/vcl/"))) {
			ipc_run(vcl->vadmin, &vret, "vcl.show %s", request->url + strlen("/vcl/"));
			if (vret.status == 400) {
				http_reply(request->connection, 500, vret.answer);
			} else {
				http_reply(request->connection, 200, vret.answer);
			}
			free(vret.answer);
			return 0;
		} else if(!strcmp(request->url, "/vcljson/")) {
			struct vsb *json;
			ipc_run(vcl->vadmin, &vret, "vcl.list");
			if (vret.status == 400) {
				http_reply(request->connection, 500, vret.answer);
			} else {
				json = vcl_list_json(vret.answer);
				assert(VSB_finish(json) == 0);
				resp = http_mkresp(request->connection, 200, NULL);
				resp->data = VSB_data(json);
				resp->ndata = VSB_len(json);
				http_add_header(resp, "Content-Type", "application/json");
				send_response(resp);
				http_free_resp(resp);
				VSB_clear(json);
				VSB_delete(json);
			}
			free(vret.answer);
			return 0;
		} else {
			http_reply(request->connection, 500, "Invalid VCL-url.");
			return 0;
		}
	} else if (request->method == M_POST) {
		snprintf(id, sizeof(id), "%ju", (uintmax_t) time(NULL));
		status = vcl_store(request, vcl, &vret, core, id);
		http_reply(request->connection, status, vret.answer);
		free(vret.answer);
		return 0;
	} else if (request->method == M_PUT) {
		if (!strncmp(request->url,"/vcl/",strlen("/vcl/"))) {
			if (strlen(request->url) >= 6) {
				status = vcl_store(request, vcl, &vret, core,
				                   request->url + strlen("/vcl/"));
				http_reply(request->connection, status, vret.answer);
				free(vret.answer);
				return 0;
			} else {
				http_reply(request->connection, 400, "Bad URL?");
				return 0;
			}
		} else if (!strncmp(request->url, "/vcldeploy/",strlen("/vcldeploy/"))) {
			ipc_run(vcl->vadmin, &vret, "vcl.use %s",
				request->url + strlen("/vcldeploy/"));
			if (vret.status == 200) {
				ret = vcl_persist_active(vcl->logger, request->url + strlen("/vcldeploy/"), core);
			}
			if (vret.status == 200 && ret)
				http_reply(request->connection, 500, "Deployed ok, but NOT PERSISTED.");
			else if (vret.status == 200 && ret == 0)
				http_reply(request->connection, 200, vret.answer);
			else
				http_reply(request->connection, 500, vret.answer);
			free(vret.answer);
			return 0;
		}
	} else if (request->method == M_DELETE) {
		if (!strncmp(request->url, "/vcl/", strlen("/vcl/"))) {
			ipc_run(vcl->vadmin, &vret, "vcl.discard %s",
				request->url + strlen("/vcl/"));
			if (vret.status == 400 || vret.status == 106) {
				http_reply(request->connection, 500, vret.answer);
			} else {
				http_reply(request->connection, 200, vret.answer);
			}
			free(vret.answer);
			return 0;
		}
	} else {
		return http_reply(request->connection, 500, "Unknown request?");
	}
	assert("Shouldn't get here" == NULL);
	return 0;
}