/** * @file kHttpServerResponse.cpp * * @internal * Copyright (C) 2013-2021 by LMI Technologies Inc. * Licensed under the MIT License. * Redistributed files must retain the above copyright notice. */ #include #include #include #include #include #include kBeginEnumEx(k, kHttpStatus) kAddEnumerator(kHttpStatus, kHTTP_STATUS_CONTINUE) kAddEnumerator(kHttpStatus, kHTTP_STATUS_SWITCHING_PROTOCOLS) kAddEnumerator(kHttpStatus, kHTTP_STATUS_OK) kAddEnumerator(kHttpStatus, kHTTP_STATUS_CREATED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_ACCEPTED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_NON_AUTHORITATIVE) kAddEnumerator(kHttpStatus, kHTTP_STATUS_NO_CONTENT) kAddEnumerator(kHttpStatus, kHTTP_STATUS_RESET_CONTENT) kAddEnumerator(kHttpStatus, kHTTP_STATUS_PARTIAL_CONTENT) kAddEnumerator(kHttpStatus, kHTTP_STATUS_MULTIPLE_CHOICES) kAddEnumerator(kHttpStatus, kHTTP_STATUS_MOVED_PERMANENTLY) kAddEnumerator(kHttpStatus, kHTTP_STATUS_FOUND) kAddEnumerator(kHttpStatus, kHTTP_STATUS_SEE_OTHER) kAddEnumerator(kHttpStatus, kHTTP_STATUS_NOT_MODIFIED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_USE_PROXY) kAddEnumerator(kHttpStatus, kHTTP_STATUS_TEMPORARY_REDIRECT) kAddEnumerator(kHttpStatus, kHTTP_STATUS_BAD_REQUEST) kAddEnumerator(kHttpStatus, kHTTP_STATUS_UNAUTHORIZED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_PAYMENT_REQUIRED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_FORBIDDEN) kAddEnumerator(kHttpStatus, kHTTP_STATUS_NOT_FOUND) kAddEnumerator(kHttpStatus, kHTTP_STATUS_METHOD_NOT_ALLOWED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_NOT_ACCEPTABLE) kAddEnumerator(kHttpStatus, kHTTP_STATUS_PROXY_AUTH_REQUIRED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_REQUEST_TIMEOUT) kAddEnumerator(kHttpStatus, kHTTP_STATUS_CONFLICT) kAddEnumerator(kHttpStatus, kHTTP_STATUS_GONE) kAddEnumerator(kHttpStatus, kHTTP_STATUS_LENGTH_REQUIRED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_PRECONDITION_FAILED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_REQUEST_ENTITY_SIZE) kAddEnumerator(kHttpStatus, kHTTP_STATUS_REQUEST_URI_SIZE) kAddEnumerator(kHttpStatus, kHTTP_STATUS_UNSUPPORTED_MEDIA_TYPE) kAddEnumerator(kHttpStatus, kHTTP_STATUS_INVALID_RANGE) kAddEnumerator(kHttpStatus, kHTTP_STATUS_EXPECTATION_FAILED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_INTERNAL_SERVER_ERROR) kAddEnumerator(kHttpStatus, kHTTP_STATUS_NOT_IMPLEMENTED) kAddEnumerator(kHttpStatus, kHTTP_STATUS_BAD_GATEWAY) kAddEnumerator(kHttpStatus, kHTTP_STATUS_SERVICE_UNAVAILABLE) kAddEnumerator(kHttpStatus, kHTTP_STATUS_GATEWAY_TIMEOUT) kAddEnumerator(kHttpStatus, kHTTP_STATUS_UNSUPPORTED_VERSION) kEndEnumEx() kBeginClassEx(k, kHttpServerResponse) kAddPrivateVMethod(kHttpServerResponse, kObject, VRelease) kEndClassEx() kFx(kStatus) xkHttpServerResponse_Construct(kHttpServerResponse* response, kHttpServerChannel channel, kAlloc allocator) { kAlloc alloc = kAlloc_Fallback(allocator); kType type = kTypeOf(kHttpServerResponse); kStatus status; kCheck(kAlloc_GetObject(alloc, type, response)); if (!kSuccess(status = xkHttpServerResponse_Init(*response, type, channel, alloc))) { kAlloc_FreeRef(alloc, response); } return status; } kFx(kStatus) xkHttpServerResponse_Init(kHttpServerResponse response, kType type, kHttpServerChannel channel, kAlloc alloc) { kObjR(kHttpServerResponse, response); kStatus status; kCheck(kObject_Init(response, type, alloc)); obj->channel = channel; obj->client = xkHttpServerChannel_Client(channel); obj->headers = kNULL; obj->version = 0; obj->status = 0; obj->reason = kNULL; obj->shouldClose = kFALSE; obj->isChunkCoded = kFALSE; obj->contentLength = 0; obj->messageStarted = kFALSE; obj->chunkIndex = 0; obj->line = kNULL; kTry { kTest(kMap_Construct(&obj->headers, kTypeOf(kString), kTypeOf(kString), 0, alloc)); kTest(kString_Construct(&obj->reason, kNULL, alloc)); kTest(kString_Construct(&obj->line, kNULL, alloc)); } kCatch(&status) { xkHttpServerResponse_VRelease(response); kEndCatch(status); } return kOK; } kFx(kStatus) xkHttpServerResponse_VRelease(kHttpServerResponse response) { kObj(kHttpServerResponse, response); kCheck(kObject_Dispose(obj->headers)); kCheck(kObject_Destroy(obj->reason)); kCheck(kObject_Destroy(obj->line)); kCheck(kObject_VRelease(response)); return kOK; } kFx(kStatus) xkHttpServerResponse_Begin(kHttpServerResponse response) { return xkHttpServerResponse_Clear(response); } kFx(kStatus) xkHttpServerResponse_Clear(kHttpServerResponse response) { kObj(kHttpServerResponse, response); kCheck(kMap_Purge(obj->headers)); kCheck(kString_Clear(obj->reason)); kCheck(kString_Clear(obj->line)); obj->version = kVersion_Create(1, 1, 0, 0); obj->status = kHTTP_STATUS_OK; obj->contentLength = -1; obj->isChunkCoded = kFALSE; obj->shouldClose = kFALSE; obj->messageStarted = kFALSE; obj->chunkIndex = 0; return kOK; } kFx(kStatus) kHttpServerResponse_SetVersion(kHttpServerResponse response, kVersion version) { kObj(kHttpServerResponse, response); obj->version = version; return kOK; } kFx(kStatus) kHttpServerResponse_SetStatus(kHttpServerResponse response, kHttpStatus status) { kObj(kHttpServerResponse, response); obj->status = status; return kOK; } kFx(kStatus) kHttpServerResponse_SetReason(kHttpServerResponse response, const kChar* reason) { kObj(kHttpServerResponse, response); return kString_Set(obj->reason, reason); } kFx(kStatus) xkHttpServerResponse_SetContentLength(kHttpServerResponse response, k64u length) { kObj(kHttpServerResponse, response); obj->contentLength = (k64s)length; return kOK; } kFx(kStatus) xkHttpServerResponse_EnableChunkCoding(kHttpServerResponse response, kBool enabled) { kObj(kHttpServerResponse, response); obj->isChunkCoded = enabled; return kOK; } kFx(kStatus) kHttpServerResponse_AddHeader(kHttpServerResponse response, const kChar* name, const kChar* value) { kObj(kHttpServerResponse, response); kMap headerMap = obj->headers; kMapItem headerItem = kNULL; kString nameStr = kNULL; kString valueStr = kNULL; kTry { kTest(kString_Construct(&nameStr, name, kObject_Alloc(response))); kTest(xkHttpServerChannel_NormalizeHeaderCaps(kString_Chars(nameStr))); if (kSuccess(kMap_FindItemT(headerMap, &nameStr, &headerItem))) { kString existingValue = kMap_ValueAsT(headerMap, headerItem, kString); kTest(kString_Addf(existingValue, ",%s", value)); } else { kTest(kString_Construct(&valueStr, value, kObject_Alloc(response))); kTest(kMap_AddT(headerMap, &nameStr, &valueStr)); nameStr = valueStr = kNULL; } } kFinally { kCheck(kObject_Destroy(nameStr)); kCheck(kObject_Destroy(valueStr)); kEndFinally(); } return kOK; } kFx(kStatus) kHttpServerResponse_SetHeader(kHttpServerResponse response, const kChar* name, const kChar* value) { kObj(kHttpServerResponse, response); kMap headerMap = obj->headers; kMapItem headerItem = kNULL; kString nameStr = kNULL; kString valueStr = kNULL; kTry { kTest(kString_Construct(&nameStr, name, kObject_Alloc(response))); kTest(xkHttpServerChannel_NormalizeHeaderCaps(kString_Chars(nameStr))); if (kSuccess(kMap_FindItemT(headerMap, &nameStr, &headerItem))) { kString existingValue = kMap_ValueAsT(headerMap, headerItem, kString); kTest(kString_Set(existingValue, value)); } else { kTest(kString_Construct(&valueStr, value, kObject_Alloc(response))); kTest(kMap_AddT(headerMap, &nameStr, &valueStr)); nameStr = valueStr = kNULL; } } kFinally { kCheck(kObject_Destroy(nameStr)); kCheck(kObject_Destroy(valueStr)); kEndFinally(); } return kOK; } kFx(kStatus) xkHttpServerResponse_BeginWriteMessage(kHttpServerResponse response) { kObj(kHttpServerResponse, response); obj->messageStarted = kTRUE; kCheck(xkHttpServerResponse_FormatStatusLine(response)); kCheck(xkHttpServerResponse_AddKnownHeaders(response)); kCheck(kHttpServerResponse_FormatHeaders(response, obj->headers)); return kOK; } kFx(kStatus) xkHttpServerResponse_AddKnownHeaders(kHttpServerResponse response) { kObj(kHttpServerResponse, response); kText128 buffer; if (obj->contentLength >= 0) { kCheck(kStrPrintf(buffer, kCountOf(buffer), "%llu", obj->contentLength)); kCheck(kHttpServerResponse_SetHeader(response, "Content-Length", buffer)); } if (obj->isChunkCoded) { kCheck(kHttpServerResponse_AddHeader(response, "Transfer-Encoding", "chunked")); } if (obj->shouldClose) { kCheck(kHttpServerResponse_AddHeader(response, "Connection", "close")); } return kOK; } const kChar* xkHttpServerResponse_DefaultReason(kHttpServerResponse response, kHttpStatus status) { switch (status) { case kHTTP_STATUS_CONTINUE: return "Continue"; case kHTTP_STATUS_SWITCHING_PROTOCOLS: return "Switching protocols"; case kHTTP_STATUS_OK: return "OK"; case kHTTP_STATUS_CREATED: return "Created"; case kHTTP_STATUS_ACCEPTED: return "Accepted"; case kHTTP_STATUS_NON_AUTHORITATIVE: return "Non-authoritative information"; case kHTTP_STATUS_NO_CONTENT: return "No content"; case kHTTP_STATUS_RESET_CONTENT: return "Reset content"; case kHTTP_STATUS_PARTIAL_CONTENT: return "Partial content"; case kHTTP_STATUS_MULTIPLE_CHOICES: return "Multiple choices"; case kHTTP_STATUS_MOVED_PERMANENTLY: return "Moved permanently"; case kHTTP_STATUS_FOUND: return "Found"; case kHTTP_STATUS_SEE_OTHER: return "See other"; case kHTTP_STATUS_NOT_MODIFIED: return "Not modified"; case kHTTP_STATUS_USE_PROXY: return "Use proxy"; case kHTTP_STATUS_TEMPORARY_REDIRECT: return "Temporary redirect"; case kHTTP_STATUS_BAD_REQUEST: return "Bad request"; case kHTTP_STATUS_UNAUTHORIZED: return "Unauthorized"; case kHTTP_STATUS_PAYMENT_REQUIRED: return "Payment required"; case kHTTP_STATUS_FORBIDDEN: return "Forbidden"; case kHTTP_STATUS_NOT_FOUND: return "Not found"; case kHTTP_STATUS_METHOD_NOT_ALLOWED: return "Method not allowed"; case kHTTP_STATUS_NOT_ACCEPTABLE: return "Not acceptable"; case kHTTP_STATUS_PROXY_AUTH_REQUIRED: return "Proxy authentication required"; case kHTTP_STATUS_REQUEST_TIMEOUT: return "Request timeout"; case kHTTP_STATUS_CONFLICT: return "Conflict"; case kHTTP_STATUS_GONE: return "Gone"; case kHTTP_STATUS_LENGTH_REQUIRED: return "Length required"; case kHTTP_STATUS_PRECONDITION_FAILED: return "Precondition failed"; case kHTTP_STATUS_REQUEST_ENTITY_SIZE: return "Request entity too large"; case kHTTP_STATUS_REQUEST_URI_SIZE: return "Request URI size too large"; case kHTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: return "Unsupported media type"; case kHTTP_STATUS_INVALID_RANGE: return "Requested range not satisfiable"; case kHTTP_STATUS_EXPECTATION_FAILED: return "Expectation failed"; case kHTTP_STATUS_INTERNAL_SERVER_ERROR: return "Internal server error"; case kHTTP_STATUS_NOT_IMPLEMENTED: return "Not implemented"; case kHTTP_STATUS_BAD_GATEWAY: return "Bad gateway"; case kHTTP_STATUS_SERVICE_UNAVAILABLE: return "Service unavailable"; case kHTTP_STATUS_GATEWAY_TIMEOUT: return "Gateway timeout"; case kHTTP_STATUS_UNSUPPORTED_VERSION: return "HTTP version not supported"; default: return "Unknown"; } } kFx(kStatus) xkHttpServerResponse_FormatStatusLine(kHttpServerResponse response) { kObj(kHttpServerResponse, response); if (kString_Length(obj->reason) == 0) { kCheck(kString_Set(obj->reason, xkHttpServerResponse_DefaultReason(response, obj->status))); } kCheck(kString_Setf(obj->line, "HTTP/%u.%u %d %s\r\n", kVersion_Major(obj->version), kVersion_Minor(obj->version), obj->status, kString_Chars(obj->reason))); kCheck(kStream_Write(obj->client, kString_Chars(obj->line), kString_Length(obj->line))); return kOK; } kFx(kStatus) kHttpServerResponse_FormatHeaders(kHttpServerResponse response, kMap headerMap) { kObj(kHttpServerResponse, response); kMapItem header = kMap_First(headerMap); //write all headers to the stream while (!kIsNull(header)) { kString name = kMap_KeyAsT(headerMap, header, kString); kString value = kMap_ValueAsT(headerMap, header, kString); kCheck(kString_Setf(obj->line, "%s: %s\r\n", kString_Chars(name), kString_Chars(value))); kCheck(kStream_Write(obj->client, kString_Chars(obj->line), kString_Length(obj->line))); header = kMap_Next(headerMap, header); } //add trailing crlf kCheck(kString_Set(obj->line, "\r\n")); kCheck(kStream_Write(obj->client, kString_Chars(obj->line), kString_Length(obj->line))); //clear out headers kCheck(kMap_Purge(obj->headers)); return kOK; } kFx(kStatus) kHttpServerResponse_SetClosed(kHttpServerResponse response, kBool closed) { kObj(kHttpServerResponse, response); obj->shouldClose = closed; return kOK; } kFx(kBool) xkHttpServerResponse_Closed(kHttpServerResponse response) { kObj(kHttpServerResponse, response); return obj->shouldClose; } kFx(kBool) xkHttpServerResponse_MessageStarted(kHttpServerResponse response) { kObj(kHttpServerResponse, response); return obj->messageStarted; } kFx(kStatus) kHttpServerResponse_BeginWriteContent(kHttpServerResponse response, k64u length, kStream* stream) { kObj(kHttpServerResponse, response); if (!kIsNull(stream)) { *stream = obj->client; } kCheckState(!obj->messageStarted); kCheck(xkHttpServerResponse_SetContentLength(response, length)); kCheck(xkHttpServerResponse_BeginWriteMessage(response)); return kOK; } kFx(kStatus) kHttpServerResponse_BeginWriteChunk(kHttpServerResponse response, k64u length, kStream* stream) { kObj(kHttpServerResponse, response); if (!kIsNull(stream)) { *stream = obj->client; } if (!obj->messageStarted) { kCheck(xkHttpServerResponse_EnableChunkCoding(response, kTRUE)); kCheck(xkHttpServerResponse_BeginWriteMessage(response)); } if (obj->chunkIndex != 0) { //write terminating CRLF from previous chunk kCheck(kString_Set(obj->line, "\r\n")); kCheck(kStream_Write(obj->client, kString_Chars(obj->line), kString_Length(obj->line))); } //write chunk length kCheck(kString_Setf(obj->line, "%llX\r\n", length)); kCheck(kStream_Write(obj->client, kString_Chars(obj->line), kString_Length(obj->line))); obj->chunkIndex++; return kOK; } kFx(kStatus) xkHttpServerResponse_End(kHttpServerResponse response) { kObj(kHttpServerResponse, response); //if the response has no body, the headers may not have been written at this point if (!obj->messageStarted) { kCheck(xkHttpServerResponse_BeginWriteMessage(response)); } //if the response has a chunk-encoded body, write any final trailing headers here if (obj->isChunkCoded) { kCheck(kHttpServerResponse_FormatHeaders(response, obj->headers)); } //flush any outgoing buffered bytes to the stream kCheck(kStream_Flush(obj->client)); return kOK; }