Пример #1
0
VCL_VOID
VRT_l_beresp_storage_hint(VRT_CTX, const char *str, ...)
{
	const char *p;
	va_list ap;
	uintptr_t sn;
	VCL_STEVEDORE stv;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(ctx->bo, BUSYOBJ_MAGIC);

	sn = WS_Snapshot(ctx->ws);
	va_start(ap, str);
	p = VRT_String(ctx->ws, NULL, str, ap);
	va_end(ap);

	if (p == NULL) {
		VSLb(ctx->vsl, SLT_LostHeader, "storage_hint");
		WS_Reset(ctx->ws, sn);
		WS_MarkOverflow(ctx->ws);
		return;
	}

	stv = VRT_stevedore(p);
	if (stv != NULL)
		ctx->bo->storage = stv;

	WS_Reset(ctx->ws, sn);
}
/*--------------------------------------------------------------------
 * The very first request
 */
static int
cnt_first(struct sess *sp)
{

    /*
     * XXX: If we don't have acceptfilters we are somewhat subject
     * XXX: to DoS'ing here.  One remedy would be to set a shorter
     * XXX: SO_RCVTIMEO and once we have received something here
     * XXX: increase it to the normal value.
     */

    assert(sp->xid == 0);
    assert(sp->restarts == 0);
    VCA_Prep(sp);

    /* Record the session watermark */
    sp->ws_ses = WS_Snapshot(sp->ws);

    /* Receive a HTTP protocol request */
    HTC_Init(sp->htc, sp->ws, sp->fd);
    sp->wrk->lastused = sp->t_open;
    sp->acct_req.sess++;

    sp->step = STP_WAIT;
    return (0);
}
Пример #3
0
static int
cnt_first(struct sess *sp)
{
    struct worker *wrk;

    CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
    wrk = sp->wrk;
    CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);

    /*
     * XXX: If we don't have acceptfilters we are somewhat subject
     * XXX: to DoS'ing here.  One remedy would be to set a shorter
     * XXX: SO_RCVTIMEO and once we have received something here
     * XXX: increase it to the normal value.
     */

    assert(sp->xid == 0);
    assert(sp->restarts == 0);
    AZ(sp->esi_level);
    VCA_Prep(sp);

    /* Record the session watermark */
    sp->ws_ses = WS_Snapshot(sp->ws);

    /* Receive a HTTP protocol request */
    HTC_Init(sp->htc, sp->ws, sp->fd, sp->vsl_id,
             cache_param->http_req_size,
             cache_param->http_req_hdr_len);
    wrk->acct_tmp.sess++;

    sp->step = STP_WAIT;
    return (0);
}
Пример #4
0
vmod_log(VRT_CTX, const char *fmt, ...)
{
	const char *p;
	va_list ap;
	uintptr_t sn;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	sn = WS_Snapshot(ctx->ws);
	va_start(ap, fmt);
	p = VRT_String(ctx->ws, NULL, fmt, ap);
	va_end(ap);

	if (p == NULL) {
		WS_MarkOverflow(ctx->ws);
		WS_Reset(ctx->ws, sn);
		return;
	}

	AN(p);
	if (ctx->vsl != NULL)
		VSLb(ctx->vsl, SLT_VCL_Log, "%s", p);
	else
		VSL(SLT_VCL_Log, 0, "%s", p);
	WS_Reset(ctx->ws, sn);
}
Пример #5
0
static void
vcl_call_method(struct worker *wrk, struct req *req, struct busyobj *bo,
    void *specific, unsigned method, vcl_func_f *func)
{
	uintptr_t aws;
	struct vrt_ctx ctx;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	INIT_OBJ(&ctx, VRT_CTX_MAGIC);
	if (req != NULL) {
		CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
		CHECK_OBJ_NOTNULL(req->sp, SESS_MAGIC);
		CHECK_OBJ_NOTNULL(req->vcl, VCL_MAGIC);
		VCL_Req2Ctx(&ctx, req);
	}
	if (bo != NULL) {
		if (req)
			assert(method == VCL_MET_PIPE);
		CHECK_OBJ_NOTNULL(bo, BUSYOBJ_MAGIC);
		CHECK_OBJ_NOTNULL(bo->vcl, VCL_MAGIC);
		VCL_Bo2Ctx(&ctx, bo);
	}
	assert(ctx.now != 0);
	ctx.syntax = ctx.vcl->conf->syntax;
	ctx.specific = specific;
	ctx.method = method;
	wrk->handling = 0;
	ctx.handling = &wrk->handling;
	aws = WS_Snapshot(wrk->aws);
	wrk->cur_method = method;
	wrk->seen_methods |= method;
	AN(ctx.vsl);
	VSLb(ctx.vsl, SLT_VCL_call, "%s", VCL_Method_Name(method));
	func(&ctx);
	VSLb(ctx.vsl, SLT_VCL_return, "%s", VCL_Return_Name(wrk->handling));
	wrk->cur_method |= 1;		// Magic marker
	if (wrk->handling == VCL_RET_FAIL)
		wrk->stats->vcl_fail++;

	/*
	 * VCL/Vmods are not allowed to make permanent allocations from
	 * wrk->aws, but they can reserve and return from it.
	 */
	assert(aws == WS_Snapshot(wrk->aws));
}
Пример #6
0
void
vmod_workspace_snap(VRT_CTX, VCL_ENUM which)
{
	struct ws *ws;
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);

	ws = wsfind(ctx, which);
	WS_Assert(ws);

	debug_ws_snap = WS_Snapshot(ws);
}
Пример #7
0
static enum fetch_step
vbf_stp_mkbereq(struct worker *wrk, struct busyobj *bo)
{
	const char *q;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(bo, BUSYOBJ_MAGIC);
	CHECK_OBJ_NOTNULL(bo->req, REQ_MAGIC);

	assert(bo->fetch_objcore->boc->state == BOS_INVALID);
	AZ(bo->storage);

	HTTP_Setup(bo->bereq0, bo->ws, bo->vsl, SLT_BereqMethod);
	http_FilterReq(bo->bereq0, bo->req->http,
	    bo->do_pass ? HTTPH_R_PASS : HTTPH_R_FETCH);

	if (!bo->do_pass) {
		http_ForceField(bo->bereq0, HTTP_HDR_METHOD, "GET");
		http_ForceField(bo->bereq0, HTTP_HDR_PROTO, "HTTP/1.1");
		if (cache_param->http_gzip_support)
			http_ForceHeader(bo->bereq0, H_Accept_Encoding, "gzip");
	} else {
		AZ(bo->stale_oc);
		if (bo->bereq0->protover > 11)
			http_ForceField(bo->bereq0, HTTP_HDR_PROTO, "HTTP/1.1");
	}
	http_CopyHome(bo->bereq0);

	if (bo->stale_oc != NULL &&
	    ObjCheckFlag(bo->wrk, bo->stale_oc, OF_IMSCAND) &&
	    (bo->stale_oc->boc != NULL || ObjGetLen(wrk, bo->stale_oc) != 0)) {
		AZ(bo->stale_oc->flags & OC_F_PASS);
		q = HTTP_GetHdrPack(bo->wrk, bo->stale_oc, H_Last_Modified);
		if (q != NULL)
			http_PrintfHeader(bo->bereq0,
			    "If-Modified-Since: %s", q);
		q = HTTP_GetHdrPack(bo->wrk, bo->stale_oc, H_ETag);
		if (q != NULL)
			http_PrintfHeader(bo->bereq0,
			    "If-None-Match: %s", q);
	}

	HTTP_Setup(bo->bereq, bo->ws, bo->vsl, SLT_BereqMethod);
	bo->ws_bo = WS_Snapshot(bo->ws);
	HTTP_Copy(bo->bereq, bo->bereq0);

	if (bo->req->req_body_status == REQ_BODY_NONE) {
		bo->req = NULL;
		ObjSetState(bo->wrk, bo->fetch_objcore, BOS_REQ_DONE);
	}
	return (F_STP_STARTFETCH);
}
Пример #8
0
vmod_syslog(VRT_CTX, VCL_INT fac, const char *fmt, ...)
{
	const char *p;
	va_list ap;
	uintptr_t sn;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	sn = WS_Snapshot(ctx->ws);
	va_start(ap, fmt);
	p = VRT_String(ctx->ws, NULL, fmt, ap);
	va_end(ap);
	if (p != NULL)
		syslog((int)fac, "%s", p);
	WS_Reset(ctx->ws, sn);
}
Пример #9
0
static enum fetch_step
vbf_stp_mkbereq(const struct worker *wrk, struct busyobj *bo)
{
	const char *q;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(bo, BUSYOBJ_MAGIC);
	CHECK_OBJ_NOTNULL(bo->req, REQ_MAGIC);

	assert(bo->state == BOS_INVALID);
	AZ(bo->storage_hint);

	HTTP_Setup(bo->bereq0, bo->ws, bo->vsl, SLT_BereqMethod);
	http_FilterReq(bo->bereq0, bo->req->http,
	    bo->do_pass ? HTTPH_R_PASS : HTTPH_R_FETCH);

	if (!bo->do_pass) {
		http_ForceField(bo->bereq0, HTTP_HDR_METHOD, "GET");
		http_ForceField(bo->bereq0, HTTP_HDR_PROTO, "HTTP/1.1");
		if (cache_param->http_gzip_support)
			http_ForceHeader(bo->bereq0, H_Accept_Encoding, "gzip");
		AN(bo->req);
		bo->req = NULL;
		http_CopyHome(bo->bereq0);
	} else
		AZ(bo->stale_oc);

	if (bo->stale_oc != NULL) {
		q = HTTP_GetHdrPack(bo->wrk, bo->stale_oc, H_Last_Modified);
		if (q != NULL)
			http_PrintfHeader(bo->bereq0,
			    "If-Modified-Since: %s", q);
		q = HTTP_GetHdrPack(bo->wrk, bo->stale_oc, H_ETag);
		if (q != NULL)
			http_PrintfHeader(bo->bereq0,
			    "If-None-Match: %s", q);
	}

	HTTP_Setup(bo->bereq, bo->ws, bo->vsl, SLT_BereqMethod);
	bo->ws_bo = WS_Snapshot(bo->ws);
	HTTP_Copy(bo->bereq, bo->bereq0);

	VBO_setstate(bo, BOS_REQ_DONE);
	return (F_STP_STARTFETCH);
}
Пример #10
0
void
V1L_Reserve(struct worker *wrk, struct ws *ws, int *fd, struct vsl_log *vsl,
    double t0)
{
	struct v1l *v1l;
	unsigned u;
	void *res;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	AZ(wrk->v1l);

	if (WS_Overflowed(ws))
		return;
	res = WS_Snapshot(ws);
	v1l = WS_Alloc(ws, sizeof *v1l);
	if (v1l == NULL)
		return;
	INIT_OBJ(v1l, V1L_MAGIC);

	v1l->ws = ws;
	v1l->res = res;

	u = WS_Reserve(ws, 0);
	u = PRNDDN(u);
	u /= sizeof(struct iovec);
	if (u == 0) {
		WS_Release(ws, 0);
		WS_MarkOverflow(ws);
		return;
	} else if (u > IOV_MAX)
		u = IOV_MAX;
	v1l->iov = (void*)PRNDUP(ws->f);
	v1l->siov = u;
	v1l->ciov = u;
	v1l->werr = 0;
	v1l->liov = 0;
	v1l->niov = 0;
	v1l->wfd = fd;
	v1l->t0 = t0;
	v1l->vsl = vsl;
	wrk->v1l = v1l;
}
Пример #11
0
static void
ved_include(struct req *preq, const char *src, const char *host,
    struct ecx *ecx)
{
	struct worker *wrk;
	struct req *req;
	enum req_fsm_nxt s;
	struct transport xp;

	CHECK_OBJ_NOTNULL(preq, REQ_MAGIC);
	CHECK_OBJ_NOTNULL(ecx, ECX_MAGIC);
	wrk = preq->wrk;

	if (preq->esi_level >= cache_param->max_esi_depth)
		return;

	req = Req_New(wrk, preq->sp);
	req->req_body_status = REQ_BODY_NONE;
	AZ(req->vsl->wid);
	req->vsl->wid = VXID_Get(wrk, VSL_CLIENTMARKER);
	VSLb(req->vsl, SLT_Begin, "req %u esi", VXID(preq->vsl->wid));
	VSLb(preq->vsl, SLT_Link, "req %u esi", VXID(req->vsl->wid));
	req->esi_level = preq->esi_level + 1;

	if (preq->esi_level == 0)
		assert(preq->top == preq);
	else
		CHECK_OBJ_NOTNULL(preq->top, REQ_MAGIC);

	req->top = preq->top;

	HTTP_Copy(req->http0, preq->http0);

	req->http0->ws = req->ws;
	req->http0->vsl = req->vsl;
	req->http0->logtag = SLT_ReqMethod;
	req->http0->conds = 0;

	http_SetH(req->http0, HTTP_HDR_URL, src);
	if (host != NULL && *host != '\0')  {
		http_Unset(req->http0, H_Host);
		http_SetHeader(req->http0, host);
	}

	http_ForceField(req->http0, HTTP_HDR_METHOD, "GET");
	http_ForceField(req->http0, HTTP_HDR_PROTO, "HTTP/1.1");

	/* Don't allow conditionalss, we can't use a 304 */
	http_Unset(req->http0, H_If_Modified_Since);
	http_Unset(req->http0, H_If_None_Match);

	/* Don't allow Range */
	http_Unset(req->http0, H_Range);

	/* Set Accept-Encoding according to what we want */
	http_Unset(req->http0, H_Accept_Encoding);
	if (ecx->isgzip)
		http_ForceHeader(req->http0, H_Accept_Encoding, "gzip");

	/* Client content already taken care of */
	http_Unset(req->http0, H_Content_Length);

	/* Reset request to status before we started messing with it */
	HTTP_Copy(req->http, req->http0);

	req->vcl = preq->vcl;
	preq->vcl = NULL;
	req->wrk = preq->wrk;

	/*
	 * XXX: We should decide if we should cache the director
	 * XXX: or not (for session/backend coupling).  Until then
	 * XXX: make sure we don't trip up the check in vcl_recv.
	 */
	req->req_step = R_STP_RECV;
	req->t_req = preq->t_req;
	assert(isnan(req->t_first));
	assert(isnan(req->t_prev));

	INIT_OBJ(&xp, TRANSPORT_MAGIC);
	xp.deliver = VED_Deliver;
	req->transport = &xp;
	req->transport_priv = ecx;

	THR_SetRequest(req);

	VSLb_ts_req(req, "Start", W_TIM_real(wrk));

	req->ws_req = WS_Snapshot(req->ws);

	while (1) {
		req->wrk = wrk;
		s = CNT_Request(wrk, req);
		if (s == REQ_FSM_DONE)
			break;
		DSL(DBG_WAITINGLIST, req->vsl->wid,
		    "loop waiting for ESI (%d)", (int)s);
		assert(s == REQ_FSM_DISEMBARK);
		AZ(req->wrk);
		(void)usleep(10000);
	}

	VRTPRIV_dynamic_kill(req->sp->privs, (uintptr_t)req);
	CNT_AcctLogCharge(wrk->stats, req);
	VSL_End(req->vsl);

	preq->vcl = req->vcl;
	req->vcl = NULL;

	req->wrk = NULL;

	THR_SetRequest(preq);
	Req_Release(req);
}
static int
cnt_start(struct sess *sp)
{
    int done;
    char *p;
    const char *r = "HTTP/1.1 100 Continue\r\n\r\n";

    CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
    AZ(sp->restarts);
    AZ(sp->obj);
    AZ(sp->vcl);

    /* Update stats of various sorts */
    VSL_stats->client_req++;			/* XXX not locked */
    sp->t_req = TIM_real();
    sp->wrk->lastused = sp->t_req;
    sp->acct_req.req++;

    /* Assign XID and log */
    sp->xid = ++xids;				/* XXX not locked */
    WSP(sp, SLT_ReqStart, "%s %s %u", sp->addr, sp->port,  sp->xid);

    /* Borrow VCL reference from worker thread */
    VCL_Refresh(&sp->wrk->vcl);
    sp->vcl = sp->wrk->vcl;
    sp->wrk->vcl = NULL;

    http_Setup(sp->http, sp->ws);
    done = http_DissectRequest(sp);

    /* Catch request snapshot */
    sp->ws_req = WS_Snapshot(sp->ws);

    /* Catch original request, before modification */
    *sp->http0 = *sp->http;

    if (done != 0) {
        sp->err_code = done;
        sp->step = STP_ERROR;
        return (0);
    }

    sp->doclose = http_DoConnection(sp->http);

    /* XXX: Handle TRACE & OPTIONS of Max-Forwards = 0 */

    /*
     * Handle Expect headers
     */
    if (http_GetHdr(sp->http, H_Expect, &p)) {
        if (strcmp(p, "100-continue")) {
            sp->err_code = 417;
            sp->step = STP_ERROR;
            return (0);
        }

        /* XXX: Don't bother with write failures for now */
        (void)write(sp->fd, r, strlen(r));
        /* XXX: When we do ESI includes, this is not removed
         * XXX: because we use http0 as our basis.  Believed
         * XXX: safe, but potentially confusing.
         */
        http_Unset(sp->http, H_Expect);
    }

    sp->step = STP_RECV;
    return (0);
}
Пример #13
0
static int
http1_dissect(struct worker *wrk, struct req *req)
{
	const char *r_100 = "HTTP/1.1 100 Continue\r\n\r\n";
	const char *r_400 = "HTTP/1.1 400 Bad Request\r\n\r\n";
	const char *r_417 = "HTTP/1.1 417 Expectation Failed\r\n\r\n";
	const char *p;
	ssize_t r;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);

	/* Allocate a new vxid now that we know we'll need it. */
	AZ(req->vsl->wid);
	req->vsl->wid = VXID_Get(wrk, VSL_CLIENTMARKER);

	VSLb(req->vsl, SLT_Begin, "req %u rxreq", VXID(req->sp->vxid));
	VSL(SLT_Link, req->sp->vxid, "req %u rxreq", VXID(req->vsl->wid));
	AZ(isnan(req->t_first)); /* First byte timestamp set by http1_wait */
	AZ(isnan(req->t_req));	 /* Complete req rcvd set by http1_wait */
	req->t_prev = req->t_first;
	VSLb_ts_req(req, "Start", req->t_first);
	VSLb_ts_req(req, "Req", req->t_req);

	/* Borrow VCL reference from worker thread */
	VCL_Refresh(&wrk->vcl);
	req->vcl = wrk->vcl;
	wrk->vcl = NULL;

	HTTP_Setup(req->http, req->ws, req->vsl, SLT_ReqMethod);
	req->err_code = HTTP1_DissectRequest(req->htc, req->http);

	/* If we could not even parse the request, just close */
	if (req->err_code != 0) {
		VSLb(req->vsl, SLT_HttpGarbage, "%.*s",
		    (int)(req->htc->rxbuf_e - req->htc->rxbuf_b),
		    req->htc->rxbuf_b);
		wrk->stats->client_req_400++;
		r = write(req->sp->fd, r_400, strlen(r_400));
		if (r > 0)
			req->acct.resp_hdrbytes += r;
		req->doclose = SC_RX_JUNK;
		return (-1);
	}

	assert (req->req_body_status == REQ_BODY_INIT);

	if (req->htc->body_status == BS_CHUNKED) {
		req->req_body_status = REQ_BODY_WITHOUT_LEN;
	} else if (req->htc->body_status == BS_LENGTH) {
		req->req_body_status = REQ_BODY_WITH_LEN;
	} else if (req->htc->body_status == BS_NONE) {
		req->req_body_status = REQ_BODY_NONE;
	} else if (req->htc->body_status == BS_EOF) {
		req->req_body_status = REQ_BODY_WITHOUT_LEN;
	} else {
		WRONG("Unknown req.body_length situation");
	}

	if (http_GetHdr(req->http, H_Expect, &p)) {
		if (strcasecmp(p, "100-continue")) {
			wrk->stats->client_req_417++;
			req->err_code = 417;
			r = write(req->sp->fd, r_417, strlen(r_417));
			if (r > 0)
				req->acct.resp_hdrbytes += r;
			req->doclose = SC_RX_JUNK;
			return (-1);
		}
		r = write(req->sp->fd, r_100, strlen(r_100));
		if (r > 0)
			req->acct.resp_hdrbytes += r;
		if (r != strlen(r_100)) {
			req->doclose = SC_REM_CLOSE;
			return (-1);
		}
		http_Unset(req->http, H_Expect);
	}

	wrk->stats->client_req++;
	wrk->stats->s_req++;

	AZ(req->err_code);
	req->ws_req = WS_Snapshot(req->ws);

	req->doclose = http_DoConnection(req->http);
	if (req->doclose == SC_RX_BAD) {
		r = write(req->sp->fd, r_400, strlen(r_400));
		if (r > 0)
			req->acct.resp_hdrbytes += r;
		return (-1);
	}

	assert(req->req_body_status != REQ_BODY_INIT);

	HTTP_Copy(req->http0, req->http);	// For ESI & restart

	return (0);
}
Пример #14
0
static const char *
sort_querystring(struct ws *ws, const char *uri)
{
	if (uri == NULL) {
		return NULL;
	}

	char *query_string = strchr(uri, '?');
	if (query_string == NULL) {
		return uri;
	}

	if (query_string[1] == '\0') {
		return truncate_querystring(ws, uri, query_string);
	}

	/* reserve some memory */
	char *snapshot = WS_Snapshot(ws);
	char *sorted_uri = WS_Alloc(ws, strlen(uri) + 1);

	WS_Assert(ws);

	if (sorted_uri == NULL) {
		WS_Reset(ws, snapshot);
		return uri;
	}

	unsigned available = WS_Reserve(ws, 0);
	struct query_param *params = (struct query_param*) ws->f;
	struct query_param *end = params + available;

	/* initialize the params array */
	int head = 10;

	if (&params[head + 1] > end) {
		head = 0;
	}

	if (&params[head + 1] > end) {
		WS_Release(ws, 0);
		WS_Reset(ws, snapshot);
		return uri;
	}

	int tail = head;
	int last_param = head;

	/* search and sort params */
	bool sorted = true;
	char *c = query_string + 1;
	params[head].value = c;

	for (; *c != '\0' && &params[tail+1] < end; c++) {
		if (*c != '&') {
			continue;
		}

		const char *current_param = c+1;
		params[last_param].length = c - params[last_param].value;

		if (head > 0 && compare_params(params[head].value, current_param) > -1) {
			sorted = false;
			params[--head].value = current_param;
			last_param = head;
			continue;
		}

		if (compare_params(params[tail].value, current_param) < 1) {
			params[++tail].value = current_param;
			last_param = tail;
			continue;
		}

		sorted = false;

		int i = tail++;
		params[tail] = params[i];

		int previous = i-1;
		while (i > head && compare_params(params[previous].value, current_param) > -1) {
			params[i--] = params[previous--];
		}

		params[i].value = current_param;
		last_param = i;
	}

	if (sorted == true || &params[tail+1] >= end || tail - head < 1) {
		WS_Release(ws, 0);
		WS_Reset(ws, snapshot);
		return uri;
	}

	params[last_param].length = c - params[last_param].value;

	/* copy the url parts */
	char *position = mempcpy(sorted_uri, uri, query_string - uri + 1);
	int count = tail-head;

	for (;count > 0; count--, ++head) {
		if (params[head].length > 0) {
			position = mempcpy(position, params[head].value, params[head].length);
			*position++ = '&';
		}
	}

	if (params[head].length > 0) {
		position = mempcpy(position, params[head].value, params[head].length);
	}
	else {
		position--;
	}

	*position = '\0';

	WS_Release(ws, 0);
	return sorted_uri;
}