Mercurial
comparison seobeo/s_web.c @ 22:947b81010aba
[Dowa & Seobeo] Updated so that Dowa hashmaps can use arena and not be broken. Split up web so taht it can handle different paths. Also fixes issues with hash collisions which was pain in the ass.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Tue, 07 Oct 2025 07:11:02 -0700 |
| parents | 09def63429b9 |
| children | c0f6c8c7829f |
comparison
equal
deleted
inserted
replaced
| 21:09def63429b9 | 22:947b81010aba |
|---|---|
| 60 { | 60 { |
| 61 Dowa_PHashEntry entry = NULL; | 61 Dowa_PHashEntry entry = NULL; |
| 62 Dowa_PHashMap p_current = p_html_cache; | 62 Dowa_PHashMap p_current = p_html_cache; |
| 63 char *slash; | 63 char *slash; |
| 64 | 64 |
| 65 Dowa_PArena p_response_arena = Dowa_Arena_Create(8192); | 65 Dowa_PArena p_response_arena = Dowa_Arena_Create(1*1024*1024); |
| 66 if (!p_response_arena) { perror("Dowa_Arena_Initialize"); goto clean_up; } | 66 if (!p_response_arena) { perror("Dowa_Arena_Initialize"); goto clean_up; } |
| 67 | 67 |
| 68 void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)2048); | 68 void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)2048); |
| 69 if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; } | 69 if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; } |
| 70 | 70 |
| 71 Dowa_PHashMap p_req_map = Dowa_HashMap_Create_With_Arena(32, p_response_arena); | 71 // Parse request headers into hashmap |
| 72 Dowa_PHashMap p_req_map = Dowa_HashMap_Create_With_Arena(100, p_response_arena); | |
| 72 if (Seobeo_Web_Header_Parse(p_cli_handle, p_req_map) != 0) | 73 if (Seobeo_Web_Header_Parse(p_cli_handle, p_req_map) != 0) |
| 73 { | 74 { |
| 74 // malformed request or closed — respond 400 | 75 Seobeo_Web_Header_Generate(p_response_header, |
| 75 Seobeo_Web_Header_Generate(p_response_header, | 76 HTTP_BAD_REQUEST, |
| 76 HTTP_BAD_REQUEST, | 77 "text/plain", 0); |
| 77 "text/plain", 0); | 78 Seobeo_Handle_Queue(p_cli_handle, |
| 78 Seobeo_Handle_Queue(p_cli_handle, | 79 (const uint8*)p_response_header, |
| 79 (const uint8*)p_response_header, | 80 (uint32)strlen(p_response_header)); |
| 80 (uint32)strlen(p_response_header)); | |
| 81 Seobeo_Handle_Flush(p_cli_handle); | 81 Seobeo_Handle_Flush(p_cli_handle); |
| 82 goto clean_up; | 82 goto clean_up; |
| 83 } | 83 } |
| 84 | 84 |
| 85 // DEBUG | 85 Dowa_HashMap_Print(p_req_map); |
| 86 // Dowa_HashMap_Print(p_req_map); | 86 |
| 87 | 87 // Extract method (GET, POST, etc.) |
| 88 const char *path = (const char*)Dowa_HashMap_Get(p_req_map, "Path"); | 88 const char *method = (const char*)Dowa_HashMap_Get(p_req_map, "HTTP_Method"); |
| 89 char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)512); | 89 printf("Method: %s Pointer %p\n\n", method, p_req_map); |
| 90 | 90 if (!method) |
| 91 if (!path || strcmp(path, "/") == 0) | 91 { |
| 92 { | 92 printf("?? wtf\n\n"); |
| 93 strcpy(file_path, "index.html"); | 93 Seobeo_Web_Header_Generate(p_response_header, |
| 94 }else | 94 HTTP_BAD_REQUEST, |
| 95 { | 95 "text/plain", 0); |
| 96 size_t L = strlen(path); | 96 Seobeo_Handle_Queue(p_cli_handle, |
| 97 // strip leading '/' | 97 (const uint8*)p_response_header, |
| 98 if (path[0] == '/') | 98 (uint32)strlen(p_response_header)); |
| 99 { | 99 Seobeo_Handle_Flush(p_cli_handle); |
| 100 if (strchr(path, '.') == NULL) | 100 goto clean_up; |
| 101 snprintf(file_path, 512, "%.*s/index.html", (int)(L-1), path+1); | 101 } |
| 102 | |
| 103 // --- Separate GET map for caching or routing --- | |
| 104 Dowa_PHashMap p_get_map = Dowa_HashMap_Create(64); | |
| 105 if (!p_get_map) | |
| 106 { | |
| 107 perror("Dowa_HashMap_Create (p_get_map)"); | |
| 108 goto clean_up; | |
| 109 } | |
| 110 | |
| 111 // --- Handle different HTTP methods --- | |
| 112 if (strcmp(method, "GET") == 0) | |
| 113 { | |
| 114 const char *path = (const char*)Dowa_HashMap_Get(p_req_map, "Path"); | |
| 115 char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)512); | |
| 116 | |
| 117 if (!path || strcmp(path, "/") == 0) | |
| 118 { | |
| 119 strcpy(file_path, "index.html"); | |
| 120 } | |
| 121 else | |
| 122 { | |
| 123 size_t L = strlen(path); | |
| 124 if (path[0] == '/') | |
| 125 { | |
| 126 if (strchr(path, '.') == NULL) | |
| 127 snprintf(file_path, 512, "%.*s/index.html", (int)(L-1), path+1); | |
| 128 else | |
| 129 snprintf(file_path, 512, "%.*s", (int)(L-1), path+1); | |
| 130 } | |
| 102 else | 131 else |
| 103 snprintf(file_path, 512, "%.*s", (int)(L-1), path+1); | 132 { |
| 104 }else | 133 strcpy(file_path, path); |
| 105 { | 134 } |
| 106 // Probably never get here? | 135 } |
| 107 strcpy(file_path, path); | 136 |
| 108 } | 137 // Store path for GET handling map |
| 109 } | 138 Dowa_HashMap_Push_Value(p_get_map, "Path", file_path, strlen(file_path) + 1); |
| 110 | 139 |
| 111 // DEBUG | 140 // Walk through nested maps to find content |
| 112 // printf("\n\nfile_path: %s\n", file_path); | 141 while ((slash = strchr(file_path, '/'))) |
| 113 | 142 { |
| 114 // Recursively go though the path until it gets to a file | 143 *slash = '\0'; |
| 115 while ((slash = strchr(file_path, '/'))) | 144 char *dir = file_path; |
| 116 { | 145 file_path = slash + 1; |
| 117 *slash = '\0'; // e.g. file_path="foo", slash+1="index.html" | 146 |
| 118 char *dir = file_path; // "foo" | 147 printf("Directory: %s\n", dir); |
| 119 file_path = slash + 1; // "index.html" | 148 |
| 120 | 149 p_current = Dowa_HashMap_Get(p_current, dir); |
| 121 printf("\n\nDirectory: %s\n\n", dir); | 150 if (!p_current) |
| 122 | 151 { |
| 123 p_current = Dowa_HashMap_Get(p_current, dir); | 152 fprintf(stderr, "No value in hashmap key: %s\n\n", dir); |
| 124 if (!p_current) | 153 Seobeo_Web_Header_Generate(p_response_header, |
| 125 { | 154 HTTP_NOT_FOUND, |
| 126 fprintf(stderr, "No value in hashmap key: %s\n\n", dir); | 155 "text/html", 0); |
| 156 Seobeo_Handle_Queue(p_cli_handle, | |
| 157 (const uint8*)p_response_header, | |
| 158 (uint32)strlen(p_response_header)); | |
| 159 Seobeo_Handle_Flush(p_cli_handle); | |
| 160 goto clean_up; | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 size_t pos = Dowa_HashMap_Get_Position(p_current, file_path); | |
| 165 entry = p_current->entries[pos]; | |
| 166 | |
| 167 if (!entry) | |
| 168 { | |
| 127 Seobeo_Web_Header_Generate(p_response_header, | 169 Seobeo_Web_Header_Generate(p_response_header, |
| 128 HTTP_NOT_FOUND, | 170 HTTP_NOT_FOUND, |
| 129 "text/html", 0); | 171 "text/html", 0); |
| 130 Seobeo_Handle_Queue(p_cli_handle, | 172 Seobeo_Handle_Queue(p_cli_handle, |
| 131 (const uint8*)p_response_header, | 173 (const uint8*)p_response_header, |
| 132 (uint32)strlen(p_response_header)); | 174 (uint32)strlen(p_response_header)); |
| 133 Seobeo_Handle_Flush(p_cli_handle); | 175 Seobeo_Handle_Flush(p_cli_handle); |
| 134 goto clean_up; | 176 goto clean_up; |
| 135 } | 177 } |
| 136 } | 178 |
| 137 | 179 const char *mime = "application/octet-stream"; |
| 138 size_t pos = Dowa_HashMap_Get_Position(p_current, file_path); | 180 if (strstr(file_path, ".html")) mime = "text/html; charset=utf-8"; |
| 139 entry = p_current->entries[pos]; | 181 else if (strstr(file_path, ".css")) mime = "text/css"; |
| 140 | 182 else if (strstr(file_path, ".js")) mime = "application/javascript"; |
| 141 // Missing so 404 | 183 else if (strstr(file_path, ".png")) mime = "image/png"; |
| 142 if (!entry) | 184 else if (strstr(file_path, ".jpg") || strstr(file_path, ".jpeg")) mime = "image/jpeg"; |
| 143 { | 185 else if (strstr(file_path, ".gif")) mime = "image/gif"; |
| 144 Seobeo_Web_Header_Generate(p_response_header, | 186 else if (strstr(file_path, ".svg")) mime = "image/svg+xml"; |
| 145 HTTP_NOT_FOUND, | 187 else if (strstr(file_path, ".ico")) mime = "image/x-icon"; |
| 146 "text/html", 0); | 188 else if (strstr(file_path, ".json")) mime = "application/json"; |
| 147 Seobeo_Handle_Queue(p_cli_handle, | 189 |
| 148 (const uint8*)p_response_header, | 190 size_t body_size = entry->capacity; |
| 149 (uint32)strlen(p_response_header)); | 191 printf("key: %s\nBody Size: %zu\n", entry->key, body_size); |
| 150 Seobeo_Handle_Flush(p_cli_handle); | 192 |
| 151 goto clean_up; | 193 Seobeo_Web_Header_Generate(p_response_header, |
| 152 } | 194 HTTP_OK, |
| 153 | 195 mime, |
| 154 | 196 body_size); |
| 155 const char *mime = "application/octet-stream"; // Default binary | 197 |
| 156 if (strstr(file_path, ".html")) mime = "text/html; charset=utf-8"; | 198 Seobeo_Handle_Queue(p_cli_handle, |
| 157 else if (strstr(file_path, ".css")) mime = "text/css"; | 199 (const uint8*)p_response_header, |
| 158 else if (strstr(file_path, ".js")) mime = "application/javascript"; | 200 (uint32)strlen(p_response_header)); |
| 159 else if (strstr(file_path, ".png")) mime = "image/png"; | 201 Seobeo_Handle_Queue(p_cli_handle, |
| 160 else if (strstr(file_path, ".jpg") || strstr(file_path, ".jpeg")) mime = "image/jpeg"; | 202 (const uint8*)entry->buffer, |
| 161 else if (strstr(file_path, ".gif")) mime = "image/gif"; | 203 (uint32)body_size); |
| 162 else if (strstr(file_path, ".svg")) mime = "image/svg+xml"; | 204 Seobeo_Handle_Flush(p_cli_handle); |
| 163 else if (strstr(file_path, ".ico")) mime = "image/x-icon"; | 205 } |
| 164 else if (strstr(file_path, ".json")) mime = "application/json"; | 206 else if (strcmp(method, "POST") == 0) |
| 165 | 207 { |
| 166 size_t body_size = entry->capacity; | 208 // --- TODO: Add POST logic here --- |
| 167 printf("key: %s\n\n", entry->key); | 209 Seobeo_Web_Header_Generate(p_response_header, |
| 168 printf("Body Size: %zu\n\n", body_size); | 210 HTTP_NOT_FOUND, |
| 169 Seobeo_Web_Header_Generate(p_response_header, | 211 "text/plain", 0); |
| 170 HTTP_OK, | 212 Seobeo_Handle_Queue(p_cli_handle, |
| 171 mime, | 213 (const uint8*)p_response_header, |
| 172 body_size); | 214 (uint32)strlen(p_response_header)); |
| 173 | 215 Seobeo_Handle_Flush(p_cli_handle); |
| 174 printf("response header: %s, data: %d\n\n", p_response_header, (uint32)strlen(p_response_header)); | 216 } |
| 175 Seobeo_Handle_Queue(p_cli_handle, | 217 else if (strcmp(method, "PUT") == 0) |
| 176 (const uint8*)p_response_header, | 218 { |
| 177 (uint32)strlen(p_response_header)); | 219 // --- TODO: Add PUT logic here --- |
| 178 | 220 Seobeo_Web_Header_Generate(p_response_header, |
| 179 printf("response body data: %d\n\n", (uint32)body_size); | 221 HTTP_NOT_FOUND, |
| 180 Seobeo_Handle_Queue(p_cli_handle, | 222 "text/plain", 0); |
| 181 (const uint8*)entry->buffer, | 223 Seobeo_Handle_Queue(p_cli_handle, |
| 182 (uint32)body_size); | 224 (const uint8*)p_response_header, |
| 183 Seobeo_Handle_Flush(p_cli_handle); | 225 (uint32)strlen(p_response_header)); |
| 226 Seobeo_Handle_Flush(p_cli_handle); | |
| 227 } | |
| 228 else if (strcmp(method, "DELETE") == 0) | |
| 229 { | |
| 230 // --- TODO: Add DELETE logic here --- | |
| 231 Seobeo_Web_Header_Generate(p_response_header, | |
| 232 HTTP_NOT_FOUND, | |
| 233 "text/plain", 0); | |
| 234 Seobeo_Handle_Queue(p_cli_handle, | |
| 235 (const uint8*)p_response_header, | |
| 236 (uint32)strlen(p_response_header)); | |
| 237 Seobeo_Handle_Flush(p_cli_handle); | |
| 238 } | |
| 239 else | |
| 240 { | |
| 241 // Unknown or unsupported method | |
| 242 Seobeo_Web_Header_Generate(p_response_header, | |
| 243 HTTP_FORBIDDEN, | |
| 244 "text/plain", 0); | |
| 245 Seobeo_Handle_Queue(p_cli_handle, | |
| 246 (const uint8*)p_response_header, | |
| 247 (uint32)strlen(p_response_header)); | |
| 248 Seobeo_Handle_Flush(p_cli_handle); | |
| 249 } | |
| 184 | 250 |
| 185 clean_up: | 251 clean_up: |
| 186 if (p_cli_handle) | 252 if (p_cli_handle) |
| 187 Seobeo_Handle_Destroy(p_cli_handle); | 253 Seobeo_Handle_Destroy(p_cli_handle); |
| 188 if (p_response_arena) | 254 if (p_response_arena) |
| 189 Dowa_Arena_Destroy(p_response_arena); | 255 Dowa_Arena_Destroy(p_response_arena); |
| 190 } | 256 } |
| 191 | 257 |
| 258 | |
| 192 int Seobeo_Web_Header_Parse(Seobeo_PHandle p_handle, Dowa_PHashMap map) | 259 int Seobeo_Web_Header_Parse(Seobeo_PHandle p_handle, Dowa_PHashMap map) |
| 193 { | 260 { |
| 194 // 1) Fill read_buffer until we see "\r\n\r\n" | 261 // 1) Fill read_buffer until we see "\r\n\r\n" |
| 195 while (1) | 262 while (1) |
| 196 { | 263 { |
| 209 // 2) Parse request‐line "METHOD SP PATH SP VERSION CRLF" | 276 // 2) Parse request‐line "METHOD SP PATH SP VERSION CRLF" |
| 210 char *buf = (char*)p_handle->read_buffer; | 277 char *buf = (char*)p_handle->read_buffer; |
| 211 char *hdr_end = strstr(buf, "\r\n\r\n"); | 278 char *hdr_end = strstr(buf, "\r\n\r\n"); |
| 212 size_t hdr_len = hdr_end - buf + 4; | 279 size_t hdr_len = hdr_end - buf + 4; |
| 213 | 280 |
| 281 // This seems kinda bad ? | |
| 214 char method[16], path[256], version[16]; | 282 char method[16], path[256], version[16]; |
| 215 if (sscanf(buf, "%15s %255s %15s", method, path, version) != 3) | 283 if (sscanf(buf, "%15s %255s %15s", method, path, version) != 3) |
| 216 { | 284 { |
| 217 return -1; | 285 return -1; |
| 218 } | 286 } |
| 219 | 287 |
| 220 Dowa_HashMap_Push_Value_With_Type(map, "Method", method, strlen(method) + 1, DOWA_HASH_MAP_TYPE_STRING); | 288 Dowa_HashMap_Push_Value_With_Type(map, "HTTP_Method", method, strlen(method) + 1, DOWA_HASH_MAP_TYPE_STRING); |
| 221 Dowa_HashMap_Push_Value_With_Type(map, "Path", path, strlen(path) + 1, DOWA_HASH_MAP_TYPE_STRING); | 289 printf("Method: %s Pointer %p\n\n", Dowa_HashMap_Get(map, "HTTP_Method"), map); |
| 222 Dowa_HashMap_Push_Value_With_Type(map, "Version", version, strlen(version) + 1, DOWA_HASH_MAP_TYPE_STRING); | 290 Dowa_HashMap_Push_Value_With_Type(map, "Version", version, strlen(version) + 1, DOWA_HASH_MAP_TYPE_STRING); |
| 291 | |
| 292 // 1) Separate raw path and query string | |
| 293 char *raw_path = path; | |
| 294 char *query_start = strchr(raw_path, '?'); | |
| 295 char *query_str = NULL; | |
| 296 | |
| 297 if (query_start) | |
| 298 { | |
| 299 *query_start = '\0'; // now raw_path ends before '?' | |
| 300 query_str = query_start + 1; | |
| 301 } | |
| 302 | |
| 303 // push only the clean path | |
| 304 Dowa_HashMap_Push_Value_With_Type( | |
| 305 map, | |
| 306 "Path", | |
| 307 raw_path, | |
| 308 strlen(raw_path) + 1, | |
| 309 DOWA_HASH_MAP_TYPE_STRING); | |
| 310 | |
| 311 // 2) If there *is* a query, tokenize into a sub-map | |
| 312 if (query_str && *query_str) | |
| 313 { | |
| 314 // create nested map for GET params | |
| 315 Dowa_PHashMap p_query_map = Dowa_HashMap_Create_With_Arena(100, map->p_arena); | |
| 316 | |
| 317 char *cur = query_str; | |
| 318 while (cur && *cur) | |
| 319 { | |
| 320 // find the next '&' | |
| 321 char *next_amp = strchr(cur, '&'); | |
| 322 // if none, treat end-of-string as the boundary | |
| 323 char *pair_end = next_amp ? next_amp : cur + strlen(cur); | |
| 324 | |
| 325 // find '=' in [cur, pair_end) | |
| 326 char *eq = memchr(cur, '=', pair_end - cur); | |
| 327 if (eq) { | |
| 328 size_t key_len = eq - cur; | |
| 329 size_t val_len = pair_end - (eq + 1); | |
| 330 | |
| 331 // extract key | |
| 332 char key_buf[key_len + 1]; | |
| 333 memcpy(key_buf, cur, key_len); | |
| 334 key_buf[key_len] = '\0'; | |
| 335 | |
| 336 // extract value | |
| 337 char val_buf[val_len + 1]; | |
| 338 memcpy(val_buf, eq + 1, val_len); | |
| 339 val_buf[val_len] = '\0'; | |
| 340 | |
| 341 printf("key: '%s', value: '%s'\n", key_buf, val_buf); | |
| 342 // push into map with strlen(val_buf)+1 to include '\0' | |
| 343 Dowa_HashMap_Push_Value_With_Type( | |
| 344 p_query_map, | |
| 345 key_buf, | |
| 346 val_buf, | |
| 347 (uint32_t)(val_len + 1), | |
| 348 DOWA_HASH_MAP_TYPE_STRING); | |
| 349 } | |
| 350 | |
| 351 // advance past '&' if present, else end loop | |
| 352 cur = next_amp ? next_amp + 1 : NULL; | |
| 353 } | |
| 354 if ( | |
| 355 Dowa_HashMap_Push_Value_With_Type_NoCopy( | |
| 356 map, | |
| 357 "QueryParams", | |
| 358 p_query_map, | |
| 359 sizeof(p_query_map), | |
| 360 DOWA_HASH_MAP_TYPE_HASHMAP) == -1 | |
| 361 ) | |
| 362 { | |
| 363 printf("Something went wrong...\n\n"); | |
| 364 } | |
| 365 } | |
| 366 | |
| 367 // int qp = Dowa_HashMap_Get_Position(map, "QueryParams"); | |
| 368 // Dowa_PHashEntry p_qp_entry = map->entries[qp]; | |
| 369 // printf("query param key: %s\n", p_qp_entry->key); | |
| 370 // printf("query param value: %s\n",(char *)Dowa_HashMap_Get(p_qp_entry->buffer, "hello")); | |
| 223 | 371 |
| 224 // 3) Parse each header line until the blank line | 372 // 3) Parse each header line until the blank line |
| 225 char *line = buf + strlen(method) + 1 + strlen(path) + 1 + strlen(version) + 2; | 373 char *line = buf + strlen(method) + 1 + strlen(path) + 1 + strlen(version) + 2; |
| 226 while (line < hdr_end) | 374 while (line < hdr_end) |
| 227 { | 375 { |
| 228 char *next = strstr(line, "\r\n"); | 376 char *next = strstr(line, "\r\n"); |
| 229 if (!next) break; | 377 if (!next) break; |
| 230 | 378 |
| 231 // split at colon | 379 // split at colon |
| 232 char *colon = memchr(line, ':', next - line); | 380 char *colon = memchr(line, ':', next - line); |
| 233 if (colon) { | 381 if (colon) |
| 382 { | |
| 234 size_t key_len = colon - line; | 383 size_t key_len = colon - line; |
| 235 size_t value_len = next - colon - 1; | 384 size_t value_len = next - colon - 1; |
| 236 | 385 |
| 237 char *val_start = colon + 1; | 386 char *val_start = colon + 1; |
| 238 if (*val_start == ' ') | 387 if (*val_start == ' ') |
| 247 | 396 |
| 248 char *val = malloc(value_len + 1); | 397 char *val = malloc(value_len + 1); |
| 249 memcpy(val, val_start, value_len); | 398 memcpy(val, val_start, value_len); |
| 250 val[value_len] = '\0'; | 399 val[value_len] = '\0'; |
| 251 | 400 |
| 252 Dowa_HashMap_Push_Value(map, key, val, value_len + 1); | 401 Dowa_HashMap_Push_Value_With_Type(map, key, val, value_len + 1, DOWA_HASH_MAP_TYPE_STRING); |
| 402 | |
| 403 printf("Capacity: %d, Length: %d ", (int)map->p_arena->capacity, (int)map->p_arena->offset); | |
| 404 printf("value_len: %d, key: %s value %s position: %d\n\n", (int)value_len + 1, key, val, Dowa_HashMap_Get_Position(map, key)); | |
| 405 printf("Method: %s Position: %d Pointer %p\n\n", Dowa_HashMap_Get(map, "HTTP_Method"), Dowa_HashMap_Get_Position(map, "HTTP_Method"), map); | |
| 253 | 406 |
| 254 free(key); | 407 free(key); |
| 255 free(val); | 408 free(val); |
| 256 } | 409 } |
| 257 | 410 |
| 412 Seobeo_Handle_Destroy(h); | 565 Seobeo_Handle_Destroy(h); |
| 413 return -1; | 566 return -1; |
| 414 } | 567 } |
| 415 } | 568 } |
| 416 | 569 |
| 417 printf("%s", p_request_body); | 570 // Debug |
| 571 printf("Request body %s", p_request_body); | |
| 418 Dowa_Arena_Destroy(p_request_arena); | 572 Dowa_Arena_Destroy(p_request_arena); |
| 419 Seobeo_Handle_Destroy(h); | 573 Seobeo_Handle_Destroy(h); |
| 420 return 0; | 574 return 0; |
| 421 } | 575 } |
| 422 | 576 |