An HTTP/1.1 server for zig
0

Configure Feed

Select the types of activity you want to include in your feed.

http.zig / src / request.zig
86 kB 2310 lines
1const std = @import("std"); 2 3const os = std.os; 4const http = @import("httpz.zig"); 5const buffer = @import("buffer.zig"); 6const metrics = @import("metrics.zig"); 7 8const Self = @This(); 9 10const posix = @import("posix.zig"); 11const Url = @import("url.zig").Url; 12const Params = @import("params.zig").Params; 13const HTTPConn = @import("worker.zig").HTTPConn; 14const Config = @import("config.zig").Config.Request; 15const StringKeyValue = @import("key_value.zig").StringKeyValue; 16const MultiFormKeyValue = @import("key_value.zig").MultiFormKeyValue; 17 18const Address = std.Io.net.IpAddress; 19const Allocator = std.mem.Allocator; 20const ArenaAllocator = std.heap.ArenaAllocator; 21 22pub const Request = struct { 23 // The URL of the request 24 url: Url, 25 26 // httpz's wrapper around a stream, the brave can access the underlying .stream 27 conn: *HTTPConn, 28 29 // the address of the client 30 address: Address, 31 32 // Path params (extracted from the URL based on the route). 33 // Using req.param(NAME) is preferred. 34 params: *Params, 35 36 // The headers of the request. Using req.header(NAME) is preferred. 37 headers: *StringKeyValue, 38 39 // The request method. 40 method: http.Method, 41 42 // The method as a string, only set when method == .OTHER, else emtpy 43 method_string: []const u8, 44 45 // The request protocol. 46 protocol: http.Protocol, 47 48 // The body of the request, if any. 49 body_buffer: ?buffer.Buffer = null, 50 body_len: usize = 0, 51 52 // The number of unread bytes from the body. This can only happen when 53 // lazy_read_size is configured and the request is larger that this value. 54 // There can still be _part_ of the body in body_buffer. 55 unread_body: usize, 56 57 // cannot use an optional on qs, because it's pre-allocated so always exists 58 qs_read: bool = false, 59 60 // The query string lookup. 61 qs: *StringKeyValue, 62 63 // cannot use an optional on fd, because it's pre-allocated so always exists 64 fd_read: bool = false, 65 66 // The formData lookup. 67 fd: *StringKeyValue, 68 69 // The multiFormData lookup. 70 mfd: *MultiFormKeyValue, 71 72 // Spare space we still have in our static buffer after parsing the request 73 // We can use this, if needed, for example to unescape querystring parameters 74 spare: []u8, 75 76 // An arena that will be reset at the end of each request. Can be used 77 // internally by this framework. The application is also free to make use of 78 // this arena. This is the same arena as response.arena. 79 arena: Allocator, 80 81 route_data: ?*const anyopaque, 82 83 // Arbitrary place for middlewares (or really anyone), to store data. 84 // Middleware can store data here while executing, and then provide a function 85 // to retrieved the [typed] data to the action. 86 middlewares: *std.StringHashMap(*anyopaque), 87 88 pub const State = Self.State; 89 90 pub fn init(arena: Allocator, conn: *HTTPConn) Request { 91 const state = &conn.req_state; 92 return .{ 93 .conn = conn, 94 .arena = arena, 95 .qs = &state.qs, 96 .fd = &state.fd, 97 .mfd = &state.mfd, 98 .method = state.method.?, 99 .unread_body = state.unread_body, 100 .method_string = state.method_string orelse "", 101 .protocol = state.protocol.?, 102 .url = Url.parse(state.url.?), 103 .address = conn.address, 104 .route_data = null, 105 .params = &state.params, 106 .headers = &state.headers, 107 .body_buffer = state.body, 108 .body_len = state.body_len, 109 .spare = state.buf[state.pos..], 110 .middlewares = &state.middlewares, 111 }; 112 } 113 114 pub fn canKeepAlive(self: *const Request) bool { 115 return switch (self.protocol) { 116 http.Protocol.HTTP11 => { 117 if (self.headers.get("connection")) |conn| { 118 return !std.mem.eql(u8, conn, "close"); 119 } 120 return true; 121 }, 122 http.Protocol.HTTP10 => return false, // TODO: support this in the cases where it can be 123 }; 124 } 125 126 pub fn body(self: *const Request) ?[]const u8 { 127 const buf = self.body_buffer orelse return null; 128 return buf.data[0..self.body_len]; 129 } 130 131 /// `name` should be full lowercase 132 pub fn header(self: *const Request, name: []const u8) ?[]const u8 { 133 return self.headers.get(name); 134 } 135 136 pub fn param(self: *const Request, name: []const u8) ?[]const u8 { 137 return self.params.get(name); 138 } 139 140 pub fn query(self: *Request) !*StringKeyValue { 141 if (self.qs_read) { 142 return self.qs; 143 } 144 return self.parseQuery(); 145 } 146 147 pub fn cookies(self: *const Request) Cookie { 148 return .{ 149 .header = self.header("cookie") orelse "", 150 }; 151 } 152 153 pub fn json(self: *Request, comptime T: type) !?T { 154 const b = self.body() orelse return null; 155 return try std.json.parseFromSliceLeaky(T, self.arena, b, .{}); 156 } 157 158 pub fn jsonValue(self: *Request) !?std.json.Value { 159 const b = self.body() orelse return null; 160 return try std.json.parseFromSliceLeaky(std.json.Value, self.arena, b, .{}); 161 } 162 163 pub fn jsonObject(self: *Request) !?std.json.ObjectMap { 164 const value = try self.jsonValue() orelse return null; 165 switch (value) { 166 .object => |o| return o, 167 else => return null, 168 } 169 } 170 171 pub fn formData(self: *Request) !*StringKeyValue { 172 if (self.fd_read) { 173 return self.fd; 174 } 175 return self.parseFormData(); 176 } 177 178 pub fn multiFormData(self: *Request) !*MultiFormKeyValue { 179 if (self.fd_read) { 180 return self.mfd; 181 } 182 return self.parseMultiFormData(); 183 } 184 185 pub fn reader(self: *Request, timeout_ms: usize) !Reader { 186 var buf: []const u8 = &.{}; 187 if (self.body_buffer) |bb| { 188 if (bb.type == .static) { 189 buf = bb.data; 190 } else { 191 buf = bb.data[0..self.body_len]; 192 } 193 } 194 195 const conn = self.conn; 196 if (self.unread_body > 0) { 197 try conn.blockingMode(); 198 const timeval = std.mem.toBytes(posix.timeval{ 199 .sec = @intCast(@divTrunc(timeout_ms, 1000)), 200 .usec = @intCast(@mod(timeout_ms, 1000) * 1000), 201 }); 202 try posix.setsockopt(conn.stream.socket.handle, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeval); 203 } 204 205 return .{ 206 .buffer = buf, 207 .socket = conn.stream.socket.handle, 208 .unread_body = &self.unread_body, 209 .interface = .{ 210 .end = 0, 211 .seek = 0, 212 .buffer = &.{}, 213 .vtable = &.{ 214 .stream = Reader.stream, 215 }, 216 }, 217 }; 218 } 219 220 // OK, this is a bit complicated. 221 // We might need to allocate memory to parse the querystring. Specifically, if 222 // there's a url-escaped component (a key or value), we need memory to store 223 // the un-escaped version. Ideally, we'd like to use our static buffer for this 224 // but, we might not have enough space. 225 fn parseQuery(self: *Request) !*StringKeyValue { 226 const raw = self.url.query; 227 if (raw.len == 0) { 228 self.qs_read = true; 229 return self.qs; 230 } 231 232 var qs = self.qs; 233 var buf = self.spare; 234 const allocator = self.arena; 235 236 var it = std.mem.splitScalar(u8, raw, '&'); 237 while (it.next()) |pair| { 238 if (std.mem.indexOfScalarPos(u8, pair, 0, '=')) |sep| { 239 const key_res = try Url.unescape(allocator, buf, pair[0..sep]); 240 if (key_res.buffered) { 241 buf = buf[key_res.value.len..]; 242 } 243 244 const value_res = try Url.unescape(allocator, buf, pair[sep + 1 ..]); 245 if (value_res.buffered) { 246 buf = buf[value_res.value.len..]; 247 } 248 249 qs.add(key_res.value, value_res.value); 250 } else { 251 const key_res = try Url.unescape(allocator, buf, pair); 252 if (key_res.buffered) { 253 buf = buf[key_res.value.len..]; 254 } 255 qs.add(key_res.value, ""); 256 } 257 } 258 259 self.spare = buf; 260 self.qs_read = true; 261 return self.qs; 262 } 263 264 fn parseFormData(self: *Request) !*StringKeyValue { 265 const b = self.body() orelse ""; 266 if (b.len == 0) { 267 self.fd_read = true; 268 return self.fd; 269 } 270 271 var fd = self.fd; 272 var buf = self.spare; 273 const allocator = self.arena; 274 275 var it = std.mem.splitScalar(u8, b, '&'); 276 while (it.next()) |pair| { 277 if (std.mem.indexOfScalarPos(u8, pair, 0, '=')) |sep| { 278 const key_res = try Url.unescape(allocator, buf, pair[0..sep]); 279 if (key_res.buffered) { 280 buf = buf[key_res.value.len..]; 281 } 282 283 const value_res = try Url.unescape(allocator, buf, pair[sep + 1 ..]); 284 if (value_res.buffered) { 285 buf = buf[value_res.value.len..]; 286 } 287 fd.add(key_res.value, value_res.value); 288 } else { 289 const key_res = try Url.unescape(allocator, buf, pair); 290 if (key_res.buffered) { 291 buf = buf[key_res.value.len..]; 292 } 293 fd.add(key_res.value, ""); 294 } 295 } 296 297 self.spare = buf; 298 self.fd_read = true; 299 return self.fd; 300 } 301 302 fn parseMultiFormData(self: *Request) !*MultiFormKeyValue { 303 const body_ = self.body() orelse ""; 304 if (body_.len == 0) { 305 self.fd_read = true; 306 return self.mfd; 307 } 308 309 const content_type = blk: { 310 if (self.header("content-type")) |content_type| { 311 if (std.ascii.startsWithIgnoreCase(content_type, "multipart/form-data")) { 312 break :blk content_type; 313 } 314 } 315 return error.NotMultipartForm; 316 }; 317 318 // Max boundary length is 70. Plus the two leading dashes (--) 319 var boundary_buf: [72]u8 = undefined; 320 const boundary = blk: { 321 const directive = content_type["multipart/form-data".len..]; 322 for (directive, 0..) |b, i| loop: { 323 if (b != ' ' and b != ';') { 324 if (std.ascii.startsWithIgnoreCase(directive[i..], "boundary=")) { 325 const raw_boundary = directive["boundary=".len + i ..]; 326 if (raw_boundary.len > 0 and raw_boundary.len <= 70) { 327 boundary_buf[0] = '-'; 328 boundary_buf[1] = '-'; 329 if (raw_boundary[0] == '"') { 330 if (raw_boundary.len > 2 and raw_boundary[raw_boundary.len - 1] == '"') { 331 // it's really -2, since we need to strip out the two quotes 332 // but buf is already at + 2, so they cancel out. 333 const end = raw_boundary.len; 334 @memcpy(boundary_buf[2..end], raw_boundary[1 .. raw_boundary.len - 1]); 335 break :blk boundary_buf[0..end]; 336 } 337 } else { 338 const end = 2 + raw_boundary.len; 339 @memcpy(boundary_buf[2..end], raw_boundary); 340 break :blk boundary_buf[0..end]; 341 } 342 } 343 } 344 // not valid, break out of the loop so we can return 345 // an error.InvalidMultiPartFormDataHeader 346 break :loop; 347 } 348 } 349 return error.InvalidMultiPartFormDataHeader; 350 }; 351 352 var mfd = self.mfd; 353 var entry_it = std.mem.splitSequence(u8, body_, boundary); 354 355 { 356 // We expect the body to begin with a boundary 357 const first = entry_it.next() orelse { 358 self.fd_read = true; 359 return self.mfd; 360 }; 361 if (first.len != 0) { 362 return error.InvalidMultiPartEncoding; 363 } 364 } 365 366 while (entry_it.next()) |entry| { 367 // body ends with -- after a final boundary 368 if (entry.len == 4 and entry[0] == '-' and entry[1] == '-' and entry[2] == '\r' and entry[3] == '\n') { 369 break; 370 } 371 372 if (entry.len < 2 or entry[0] != '\r' or entry[1] != '\n') return error.InvalidMultiPartEncoding; 373 374 // [2..] to skip our boundary's trailing line terminator 375 const field = try parseMultiPartEntry(entry[2..]); 376 mfd.add(field.name, field.value); 377 } 378 379 self.fd_read = true; 380 return self.mfd; 381 } 382 383 const MultiPartField = struct { 384 name: []const u8, 385 value: MultiFormKeyValue.Value, 386 }; 387 388 // I'm sorry 389 fn parseMultiPartEntry(entry: []const u8) !MultiPartField { 390 var pos: usize = 0; 391 var attributes: ?ContentDispositionAttributes = null; 392 393 while (true) { 394 const end_line_pos = std.mem.indexOfScalarPos(u8, entry, pos, '\n') orelse return error.InvalidMultiPartEncoding; 395 const line = entry[pos..end_line_pos]; 396 397 pos = end_line_pos + 1; 398 if (line.len == 0 or line[line.len - 1] != '\r') return error.InvalidMultiPartEncoding; 399 400 if (line.len == 1) { 401 break; 402 } 403 404 // we need to look for the name 405 if (std.ascii.startsWithIgnoreCase(line, "content-disposition:") == false) { 406 continue; 407 } 408 409 const value = trimLeadingSpace(line["content-disposition:".len..]); 410 if (std.ascii.startsWithIgnoreCase(value, "form-data;") == false) { 411 return error.InvalidMultiPartEncoding; 412 } 413 414 // constCast is safe here because we know this ultilately comes from one of our buffers 415 const value_start = "form-data;".len; 416 const value_end = value.len - 1; // remove the trailing \r 417 attributes = try getContentDispotionAttributes(@constCast(trimLeadingSpace(value[value_start..value_end]))); 418 } 419 420 const value = entry[pos..]; 421 if (value.len < 2 or value[value.len - 2] != '\r' or value[value.len - 1] != '\n') { 422 return error.InvalidMultiPartEncoding; 423 } 424 425 const attr = attributes orelse return error.InvalidMultiPartEncoding; 426 427 return .{ 428 .name = attr.name, 429 .value = .{ 430 .value = value[0 .. value.len - 2], 431 .filename = attr.filename, 432 }, 433 }; 434 } 435 436 const ContentDispositionAttributes = struct { 437 name: []const u8, 438 filename: ?[]const u8 = null, 439 }; 440 441 // I'm sorry 442 fn getContentDispotionAttributes(fields: []u8) !ContentDispositionAttributes { 443 var pos: usize = 0; 444 445 var name: ?[]const u8 = null; 446 var filename: ?[]const u8 = null; 447 448 while (pos < fields.len) { 449 { 450 const b = fields[pos]; 451 if (b == ';' or b == ' ' or b == '\t') { 452 pos += 1; 453 continue; 454 } 455 } 456 457 const sep = std.mem.indexOfScalarPos(u8, fields, pos, '=') orelse return error.InvalidMultiPartEncoding; 458 const field_name = fields[pos..sep]; 459 460 // skip the equal 461 const value_start = sep + 1; 462 if (value_start == fields.len) { 463 return error.InvalidMultiPartEncoding; 464 } 465 466 var value: []const u8 = undefined; 467 if (fields[value_start] != '"') { 468 const value_end = std.mem.indexOfScalarPos(u8, fields, pos, ';') orelse fields.len; 469 pos = value_end; 470 value = fields[value_start..value_end]; 471 } else blk: { 472 // skip the double quote 473 pos = value_start + 1; 474 var write_pos = pos; 475 while (pos < fields.len) { 476 switch (fields[pos]) { 477 '\\' => { 478 if (pos == fields.len) { 479 return error.InvalidMultiPartEncoding; 480 } 481 // supposedly MSIE doesn't always escape \, so if the \ isn't escape 482 // one of the special characters, it must be a single \. This is what Go does. 483 switch (fields[pos + 1]) { 484 // from Go's mime parser func isTSpecial(r rune) bool 485 '(', ')', '<', '>', '@', ',', ';', ':', '"', '/', '[', ']', '?', '=' => |n| { 486 fields[write_pos] = n; 487 pos += 1; 488 }, 489 else => fields[write_pos] = '\\', 490 } 491 }, 492 '"' => { 493 pos += 1; 494 value = fields[value_start + 1 .. write_pos]; 495 break :blk; 496 }, 497 else => |b| fields[write_pos] = b, 498 } 499 pos += 1; 500 write_pos += 1; 501 } 502 return error.InvalidMultiPartEncoding; 503 } 504 505 if (std.mem.eql(u8, field_name, "name")) { 506 name = value; 507 } else if (std.mem.eql(u8, field_name, "filename")) { 508 filename = value; 509 } 510 } 511 512 return .{ 513 .name = name orelse return error.InvalidMultiPartEncoding, 514 .filename = filename, 515 }; 516 } 517 518 pub const Reader = struct { 519 buffer: []const u8, 520 unread_body: *usize, 521 socket: posix.socket_t, 522 interface: std.Io.Reader, 523 524 pub fn stream(io_r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize { 525 const self: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); 526 const buf = limit.slice(try w.writableSliceGreedy(1)); 527 const n = self.read(buf) catch return error.ReadFailed; 528 w.advance(n); 529 return n; 530 } 531 532 pub fn read(self: *Reader, into: []u8) !usize { 533 const b = self.buffer; 534 if (b.len != 0) { 535 const l = @min(b.len, into.len); 536 @memcpy(into[0..l], b[0..l]); 537 self.buffer = b[l..]; 538 return l; 539 } 540 541 const unread = self.unread_body.*; 542 if (unread == 0) { 543 return 0; 544 } 545 546 const buf = if (into.len > unread) into[0..unread] else into; 547 const n = try posix.read(self.socket, buf); 548 self.unread_body.* = unread - n; 549 return n; 550 } 551 }; 552 553 pub const Cookie = struct { 554 header: []const u8, 555 556 pub fn get(self: Cookie, name: []const u8) ?[]const u8 { 557 var it = std.mem.splitScalar(u8, self.header, ';'); 558 while (it.next()) |kv| { 559 const trimmed = std.mem.trimStart(u8, kv, " "); 560 if (name.len >= trimmed.len) { 561 // need at least an '=' beyond the name 562 continue; 563 } 564 565 if (std.mem.startsWith(u8, trimmed, name) == false) { 566 continue; 567 } 568 if (trimmed[name.len] != '=') { 569 continue; 570 } 571 return trimmed[name.len + 1 ..]; 572 } 573 return null; 574 } 575 }; 576}; 577 578// All the upfront memory allocation that we can do. Each worker keeps a pool 579// of these to re-use. 580pub const State = struct { 581 // Header must fit in here. Extra space can be used to fit the body or decode 582 // URL parameters. 583 buf: []u8, 584 585 // position in buf that we've parsed up to 586 pos: usize, 587 588 // length of buffer for which we have valid data 589 len: usize, 590 591 // Lazy-loaded in request.query(); 592 qs: StringKeyValue, 593 594 // Lazy-loaded in request.formData(); 595 fd: StringKeyValue, 596 597 // Lazy-loaded in request.multiFormData(); 598 mfd: MultiFormKeyValue, 599 600 // Populated after we've parsed the request, once we're matching the request 601 // to a route. 602 params: Params, 603 604 // constant config 605 max_body_size: usize, 606 607 // constant config 608 lazy_read_size: ?usize, 609 610 // For reading the body, we might need more than `buf`. 611 buffer_pool: *buffer.Pool, 612 613 // URL, if we've parsed it 614 url: ?[]u8, 615 616 // Method, if we've parsed it 617 method: ?http.Method, 618 619 // Only set when method == .OTHER 620 method_string: ?[]const u8, 621 622 // Protocol, if we've parsed it 623 protocol: ?http.Protocol, 624 625 // The headers, might be partially parsed. From the outside, there's no way 626 // to know if this is fully parsed or not. There doesn't have to be. This 627 // is because once we finish parsing the headers, if there's no body, we'll 628 // signal the worker that we have a complete request and it can proceed to 629 // handle it. Thus, body == null or body_len == 0 doesn't mean anything. 630 headers: StringKeyValue, 631 632 // Our body. This be a slice pointing to` buf`, or be from the buffer_pool or 633 // be dynamically allocated. 634 body: ?buffer.Buffer, 635 636 // position in body.data that we have valid data for 637 body_pos: usize, 638 639 // the full length of the body, we might not have that much data yet, but we 640 // know what it is from the content-length header 641 body_len: usize, 642 643 // Happens when lazy_read_size is enabled and we get a large body. 644 // It'll be up to the app to read it! 645 unread_body: usize, 646 647 // Set when the request is using Transfer-Encoding: chunked. While decoding, 648 // body_pos tracks how many decoded bytes we've written into self.body.data, 649 // and body_len is set to body_pos once the terminating 0-sized chunk is seen. 650 chunked: ?Chunked, 651 652 middlewares: std.StringHashMap(*anyopaque), 653 654 const asUint = @import("url.zig").asUint; 655 656 const Chunked = struct { 657 state: Chunked.State, 658 659 // bytes left in the current chunk's data section 660 chunk_remaining: usize, 661 662 // current capacity of self.body.?.data 663 body_capacity: usize, 664 665 // Dedicated raw-input buffer. We can't recycle self.buf here because 666 // URL and header-value slices point into it; mutating it would 667 // invalidate them. 668 raw: []u8, 669 raw_pos: usize, 670 raw_len: usize, 671 672 const State = enum { 673 size_line, 674 data, 675 trailing_crlf, 676 trailers, 677 done, 678 }; 679 }; 680 681 pub fn init(arena: Allocator, buffer_pool: *buffer.Pool, config: *const Config) !Request.State { 682 return .{ 683 .pos = 0, 684 .len = 0, 685 .url = null, 686 .body = null, 687 .body_pos = 0, 688 .body_len = 0, 689 .method = null, 690 .method_string = "", 691 .protocol = null, 692 .unread_body = 0, 693 .chunked = null, 694 .buffer_pool = buffer_pool, 695 .lazy_read_size = config.lazy_read_size, 696 .max_body_size = config.max_body_size orelse 1_048_576, 697 .middlewares = std.StringHashMap(*anyopaque).init(arena), 698 .qs = try StringKeyValue.init(arena, config.max_query_count orelse 32), 699 .fd = try StringKeyValue.init(arena, config.max_form_count orelse 0), 700 .mfd = try MultiFormKeyValue.init(arena, config.max_multiform_count orelse 0), 701 .buf = try arena.alloc(u8, config.buffer_size orelse 4_096), 702 .headers = try StringKeyValue.init(arena, config.max_header_count orelse 32), 703 .params = try Params.init(arena, config.max_param_count orelse 10), 704 }; 705 } 706 707 pub fn deinit(self: *State) void { 708 if (self.body) |buf| { 709 self.buffer_pool.release(buf); 710 self.body = null; 711 } 712 } 713 714 pub fn reset(self: *State) void { 715 // not our job to clear the arena! 716 self.pos = 0; 717 self.len = 0; 718 self.url = null; 719 self.method = null; 720 self.unread_body = 0; 721 self.chunked = null; 722 self.method_string = null; 723 self.protocol = null; 724 725 self.body_pos = 0; 726 self.body_len = 0; 727 if (self.body) |buf| { 728 self.buffer_pool.release(buf); 729 self.body = null; 730 } 731 732 self.qs.reset(); 733 self.fd.reset(); 734 self.mfd.reset(); 735 self.params.reset(); 736 self.headers.reset(); 737 self.middlewares.clearRetainingCapacity(); 738 } 739 740 // returns true if the header has been fully parsed 741 pub fn parse(self: *State, conn: *HTTPConn, source: anytype) !bool { 742 if (self.body != null) { 743 if (self.chunked != null) { 744 return self.readChunked(conn, source); 745 } 746 // if we have a body, then we've read the header. We want to read into 747 // self.body, not self.buf. 748 return self.readBody(source); 749 } 750 751 var len = self.len; 752 const buf = self.buf; 753 const n = try zig016HackRead(source, buf[len..]); 754 if (n == 0) { 755 return false; 756 } 757 len = len + n; 758 self.len = len; 759 760 // When https://github.com/ziglang/zig/issues/8220 lands, we can probably 761 // DRY this up. 762 if (self.method == null) { 763 if (try self.parseMethod(buf[0..len]) == false) { 764 return false; 765 } 766 if (try self.parseUrl(buf[self.pos..len]) == false) { 767 return false; 768 } 769 if (try self.parseProtocol(buf[self.pos..len]) == false) { 770 return false; 771 } 772 if (try self.parseHeaders(conn, buf[self.pos..len]) == true) { 773 return true; 774 } 775 } else if (self.url == null) { 776 if (try self.parseUrl(buf[self.pos..len]) == false) { 777 return false; 778 } 779 if (try self.parseProtocol(buf[self.pos..len]) == false) { 780 return false; 781 } 782 if (try self.parseHeaders(conn, buf[self.pos..len]) == true) { 783 return true; 784 } 785 } else if (self.protocol == null) { 786 if (try self.parseProtocol(buf[self.pos..len]) == false) { 787 return false; 788 } 789 if (try self.parseHeaders(conn, buf[self.pos..len]) == true) { 790 return true; 791 } 792 } else { 793 if (try self.parseHeaders(conn, buf[self.pos..len]) == true) { 794 return true; 795 } 796 } 797 798 if (self.body == null and len == buf.len) { 799 metrics.headerTooBig(); 800 return error.HeaderTooBig; 801 } 802 803 return false; 804 } 805 806 fn parseMethod(self: *State, buf: []u8) !bool { 807 const buf_len = buf.len; 808 809 // Shortest method is only 3 characters (+1 trailing space), so 810 // this seems like it should be: if (buf_len < 4) 811 // But the longest method, OPTIONS, is 7 characters (+1 trailing space). 812 // Now even if we have a short method, like "GET ", we'll eventually expect 813 // a URL + protocol. The shorter valid line is: e.g. GET / HTTP/1.1 814 // If buf_len < 8, we _might_ have a method, but we still need more data 815 // and might as well break early. 816 // If buf_len > = 8, then we can safely parse any (valid) method without 817 // having to do any other bound-checking. 818 if (buf_len < 8) return false; 819 820 // this approach to matching method name comes from zhp 821 switch (@as(u32, @bitCast(buf[0..4].*))) { 822 asUint("GET ") => { 823 self.pos = 4; 824 self.method = .GET; 825 }, 826 asUint("PUT ") => { 827 self.pos = 4; 828 self.method = .PUT; 829 }, 830 asUint("POST") => { 831 if (buf[4] != ' ') return error.UnknownMethod; 832 self.pos = 5; 833 self.method = .POST; 834 }, 835 asUint("HEAD") => { 836 if (buf[4] != ' ') return error.UnknownMethod; 837 self.pos = 5; 838 self.method = .HEAD; 839 }, 840 asUint("PATC") => { 841 if (buf[4] != 'H' or buf[5] != ' ') return error.UnknownMethod; 842 self.pos = 6; 843 self.method = .PATCH; 844 }, 845 asUint("DELE") => { 846 if (@as(u32, @bitCast(buf[3..7].*)) != asUint("ETE ")) return error.UnknownMethod; 847 self.pos = 7; 848 self.method = .DELETE; 849 }, 850 asUint("OPTI") => { 851 if (@as(u32, @bitCast(buf[4..8].*)) != asUint("ONS ")) return error.UnknownMethod; 852 self.pos = 8; 853 self.method = .OPTIONS; 854 }, 855 asUint("CONN") => { 856 if (@as(u32, @bitCast(buf[4..8].*)) != asUint("ECT ")) return error.UnknownMethod; 857 self.pos = 8; 858 self.method = .CONNECT; 859 }, 860 else => { 861 const space = std.mem.indexOfScalarPos(u8, buf, 0, ' ') orelse return error.UnknownMethod; 862 if (space == 0) { 863 return error.UnknownMethod; 864 } 865 866 const candidate = buf[0..space]; 867 for (candidate) |c| { 868 if (c < 'A' or c > 'Z') { 869 return error.UnknownMethod; 870 } 871 } 872 873 // + 1 to skip the space 874 self.pos = space + 1; 875 self.method = .OTHER; 876 self.method_string = candidate; 877 }, 878 } 879 return true; 880 } 881 882 fn parseUrl(self: *State, buf: []u8) !bool { 883 const buf_len = buf.len; 884 if (buf_len == 0) return false; 885 886 var len: usize = 0; 887 switch (buf[0]) { 888 '/' => { 889 const end_index = std.mem.indexOfScalarPos(u8, buf[1..buf_len], 0, ' ') orelse return false; 890 // +1 since we skipped the leading / in our indexOfScalar and +1 to consume the space 891 len = end_index + 2; 892 const url = buf[0 .. end_index + 1]; 893 if (!Url.isValid(url)) return error.InvalidRequestTarget; 894 self.url = url; 895 }, 896 '*' => { 897 if (buf_len == 1) return false; 898 // Read never returns 0, so if we're here, buf.len >= 1 899 if (buf[1] != ' ') return error.InvalidRequestTarget; 900 len = 2; 901 self.url = buf[0..1]; 902 }, 903 // TODO: Support absolute-form target (e.g. http://....) 904 else => return error.InvalidRequestTarget, 905 } 906 907 self.pos += len; 908 return true; 909 } 910 911 fn parseProtocol(self: *State, buf: []u8) !bool { 912 if (buf.len < 10) return false; 913 914 if (@as(u32, @bitCast(buf[0..4].*)) != asUint("HTTP")) { 915 return error.UnknownProtocol; 916 } 917 918 self.protocol = switch (@as(u32, @bitCast(buf[4..8].*))) { 919 asUint("/1.1") => http.Protocol.HTTP11, 920 asUint("/1.0") => http.Protocol.HTTP10, 921 else => return error.UnsupportedProtocol, 922 }; 923 924 if (buf[8] != '\r' or buf[9] != '\n') { 925 return error.UnknownProtocol; 926 } 927 928 self.pos += 10; 929 return true; 930 } 931 932 fn parseHeaders(self: *State, conn: *HTTPConn, full: []u8) !bool { 933 var buf = full; 934 var headers = &self.headers; 935 const req_arena = conn.req_arena.allocator(); 936 937 line: while (buf.len > 0) { 938 for (buf, 0..) |bn, i| { 939 switch (bn) { 940 'a'...'z', '0'...'9', '-', '_' => {}, 941 'A'...'Z' => buf[i] = bn + 32, 942 ':' => { 943 const value_start = i + 1; // skip the colon 944 var value, const skip_len = trimLeadingSpaceCount(buf[value_start..]); 945 for (value, 0..) |bv, j| { 946 if (allowedHeaderValueByte[bv] == true) { 947 continue; 948 } 949 950 // To keep ALLOWED_HEADER_VALUE small, we said \r 951 // was illegal. I mean, it _is_ illegal in a header value 952 // but it isn't part of the header value, it's (probably) the end of line 953 if (bv != '\r') { 954 return error.InvalidHeaderLine; 955 } 956 957 const next = j + 1; 958 if (next == value.len) { 959 // we don't have any more data, we can't tell 960 return false; 961 } 962 963 if (value[next] != '\n') { 964 // we have a \r followed by something that isn't 965 // a \n. Can't be valid 966 return error.InvalidHeaderLine; 967 } 968 969 // If we're here, it means our value had valid characters 970 // up until the point of a newline (\r\n), which means 971 // we have a valid value (and name) 972 value = value[0..j]; 973 break; 974 } else { 975 // for loop reached the end without finding a \r 976 // we need more data 977 return false; 978 } 979 980 // +2 to skip the \r\n. Use the untrimmed length here so 981 // the advance still covers any trailing whitespace. 982 const next_line = value_start + skip_len + value.len + 2; 983 984 // RFC 7230 §3.2.4: OWS surrounding the field value is 985 // not part of the value. Leading was handled by 986 // trimLeadingSpaceCount; trim trailing here. 987 while (value.len > 0) { 988 const last = value[value.len - 1]; 989 if (last != ' ' and last != '\t') break; 990 value = value[0 .. value.len - 1]; 991 } 992 993 const name = buf[0..i]; 994 headers.add(name, value); 995 996 self.pos += next_line; 997 buf = buf[next_line..]; 998 continue :line; 999 }, 1000 '\r' => { 1001 if (i != 0) { 1002 // We're still parsing the header name, so a 1003 // \r should either be at the very start (to indicate the end of our headers) 1004 // or not be there at all 1005 return error.InvalidHeaderLine; 1006 } 1007 1008 if (buf.len == 1) { 1009 // we don't have any more data, we need more data 1010 return false; 1011 } 1012 1013 if (buf[1] == '\n') { 1014 // we have \r\n at the start of a line, we're done 1015 self.pos += 2; 1016 return try self.prepareForBody(conn, req_arena); 1017 } 1018 // we have a \r followed by something that isn't a \n, can't be right 1019 return error.InvalidHeaderLine; 1020 }, 1021 else => return error.InvalidHeaderLine, 1022 } 1023 } else { 1024 // didn't find a colon or blank line, we need more data 1025 return false; 1026 } 1027 } 1028 return false; 1029 } 1030 1031 // rejects duplicate Content-Length and duplicate Host 1032 // returns the Content-Length 1033 fn checkHeaderValidity(self: *State) !?[]const u8 { 1034 var content_length: ?[]const u8 = null; 1035 var host_seen: bool = false; 1036 var it = self.headers.iterator(); 1037 while (it.next()) |kv| { 1038 if (std.mem.eql(u8, kv.key, "content-length")) { 1039 if (content_length != null) return error.InvalidContentLength; 1040 content_length = kv.value; 1041 } else if (std.mem.eql(u8, kv.key, "host")) { 1042 if (host_seen) return error.InvalidHost; 1043 host_seen = true; 1044 } 1045 } 1046 return content_length; 1047 } 1048 1049 // we've finished reading the header 1050 fn prepareForBody(self: *State, conn: *HTTPConn, req_arena: Allocator) !bool { 1051 const content_length_str = try self.checkHeaderValidity(); 1052 1053 if (self.headers.get("transfer-encoding")) |te| { 1054 if (!std.ascii.eqlIgnoreCase(te, "chunked")) { 1055 return error.InvalidTransferEncoding; 1056 } 1057 // RFC 7230 §3.3.3: a sender MUST NOT send Content-Length together with 1058 // Transfer-Encoding. Reject to avoid request smuggling ambiguity. 1059 if (content_length_str != null) { 1060 return error.InvalidTransferEncoding; 1061 } 1062 if (self.headers.get("expect")) |expect| { 1063 if (std.ascii.eqlIgnoreCase(expect, "100-continue")) { 1064 try conn.writeAll("HTTP/1.1 100 Continue\r\n\r\n"); 1065 } 1066 } 1067 return self.prepareForChunkedBody(req_arena); 1068 } 1069 1070 const str = content_length_str orelse return true; 1071 const cl = atoi(str) orelse return error.InvalidContentLength; 1072 1073 if (self.headers.get("expect")) |expect| { 1074 if (std.ascii.eqlIgnoreCase(expect, "100-continue")) { 1075 // TODO: Maybe support an application-defined handler for this 1076 try conn.writeAll("HTTP/1.1 100 Continue\r\n\r\n"); 1077 } 1078 } 1079 1080 self.body_len = cl; 1081 if (cl == 0) return true; 1082 1083 if (self.lazy_read_size == null and cl > self.max_body_size) { 1084 return error.BodyTooBig; 1085 } 1086 1087 const pos = self.pos; 1088 const len = self.len; 1089 const buf = self.buf; 1090 1091 // how much (if any) of the body we've already read 1092 const read = len - pos; 1093 1094 if (read > cl) { 1095 return error.InvalidContentLength; 1096 } 1097 1098 // how much of the body are we missing 1099 const missing = cl - read; 1100 1101 if (self.lazy_read_size) |lazy_read| { 1102 if (cl >= lazy_read) { 1103 self.pos = len; 1104 self.unread_body = missing; 1105 self.body = .{ .type = .static, .data = buf[pos..len] }; 1106 return true; 1107 } 1108 } 1109 1110 if (missing == 0) { 1111 // we've read the entire body into buf, point to that. 1112 self.pos = len; 1113 self.body = .{ .type = .static, .data = buf[pos..len] }; 1114 return true; 1115 } 1116 1117 // how much spare space we have in our static buffer 1118 const spare = buf.len - len; 1119 if (missing < spare) { 1120 // we don't have the [full] body, but we have enough space in our static 1121 // buffer for it 1122 self.body = .{ .type = .static, .data = buf[pos .. pos + cl] }; 1123 1124 // While we don't have this yet, we know that this will be the final 1125 // position of valid data within self.buf. We need this so that 1126 // we create create our `spare` slice, we can slice starting from 1127 // self.pos (everything before that is the full raw request) 1128 self.pos = len + missing; 1129 } else { 1130 // We don't have the [full] body, and our static buffer is too small 1131 const body_buf = try self.buffer_pool.arenaAlloc(req_arena, cl); 1132 @memcpy(body_buf.data[0..read], buf[pos .. pos + read]); 1133 self.body = body_buf; 1134 } 1135 self.body_pos = read; 1136 return false; 1137 } 1138 1139 fn readBody(self: *State, source: anytype) !bool { 1140 const buf = self.body.?.data; 1141 self.body_pos += try zig016HackRead(source, buf[self.body_pos..]); 1142 return (self.body_pos == self.body_len); 1143 } 1144 1145 fn prepareForChunkedBody(self: *State, req_arena: Allocator) !bool { 1146 // Initial decoded-body buffer. Try the pool first; if empty, arena-alloc 1147 // a buffer of the same size so the consumer always sees a contiguous []u8 1148 // regardless of which path we took. 1149 const initial: buffer.Buffer = self.buffer_pool.tryAlloc() orelse blk: { 1150 const data = try req_arena.alloc(u8, self.buffer_pool.buffer_size); 1151 break :blk .{ .type = .arena, .data = data }; 1152 }; 1153 self.body = initial; 1154 self.body_pos = 0; 1155 self.body_len = 0; 1156 1157 // Allocate a dedicated raw-input buffer; copy whatever already sits 1158 // past the headers in self.buf. We never write back into self.buf 1159 // because URL/header-value slices reference it. 1160 const raw = try req_arena.alloc(u8, self.buf.len); 1161 const carry = self.len - self.pos; 1162 @memcpy(raw[0..carry], self.buf[self.pos..self.len]); 1163 1164 self.chunked = .{ 1165 .state = .size_line, 1166 .chunk_remaining = 0, 1167 .body_capacity = initial.data.len, 1168 .raw = raw, 1169 .raw_pos = 0, 1170 .raw_len = carry, 1171 }; 1172 // Drain anything that arrived in the same read as the headers. 1173 if (carry > 0) { 1174 return try self.processChunked(req_arena); 1175 } 1176 return false; 1177 } 1178 1179 fn readChunked(self: *State, conn: *HTTPConn, source: anytype) !bool { 1180 const req_arena = conn.req_arena.allocator(); 1181 var ck = &self.chunked.?; 1182 1183 // Compact the raw buffer to make room for the upcoming read. 1184 if (ck.raw_pos > 0) { 1185 if (ck.raw_pos == ck.raw_len) { 1186 ck.raw_pos = 0; 1187 ck.raw_len = 0; 1188 } else { 1189 const remaining = ck.raw_len - ck.raw_pos; 1190 std.mem.copyForwards(u8, ck.raw[0..remaining], ck.raw[ck.raw_pos..ck.raw_len]); 1191 ck.raw_pos = 0; 1192 ck.raw_len = remaining; 1193 } 1194 } 1195 1196 if (ck.raw_len == ck.raw.len) { 1197 // Couldn't progress and buffer is full — a chunk size line or 1198 // trailer line longer than ck.raw. Treat as malformed. 1199 return error.InvalidChunkSize; 1200 } 1201 1202 // Match readBody's shape: one read per parse() call. The worker's 1203 // event loop re-invokes us when more data arrives, so we must not 1204 // try to read past what's currently available. 1205 const n = try zig016HackRead(source, ck.raw[ck.raw_len..]); 1206 if (n == 0) { 1207 return false; 1208 } 1209 ck.raw_len += n; 1210 return try self.processChunked(req_arena); 1211 } 1212 1213 // Decodes as much of the raw buffer as possible into self.body. Returns 1214 // true once the terminating 0-sized chunk and trailers have been 1215 // consumed. Returns false to indicate "need more raw input". 1216 fn processChunked(self: *State, req_arena: Allocator) !bool { 1217 var ck = &self.chunked.?; 1218 while (true) { 1219 switch (ck.state) { 1220 .size_line => { 1221 const slice = ck.raw[ck.raw_pos..ck.raw_len]; 1222 const lf = std.mem.indexOfScalar(u8, slice, '\n') orelse { 1223 // No \r\n yet. The size line is small in practice; if we 1224 // already have more bytes than any reasonable size line, 1225 // it's malformed. 1226 if (slice.len > MAX_CHUNK_SIZE_LINE) { 1227 return error.InvalidChunkSize; 1228 } 1229 return false; 1230 }; 1231 1232 if (lf == 0 or slice[lf - 1] != '\r') { 1233 return error.InvalidChunkLine; 1234 } 1235 1236 // size = hex digits up to ';' (extension) or '\r' 1237 var end_hex: usize = 0; 1238 while (end_hex < lf and slice[end_hex] != ';' and slice[end_hex] != '\r') : (end_hex += 1) {} 1239 if (end_hex == 0) { 1240 return error.InvalidChunkSize; 1241 } 1242 1243 const size = parseHex(slice[0..end_hex]) orelse return error.InvalidChunkSize; 1244 ck.raw_pos += lf + 1; 1245 if (size == 0) { 1246 ck.state = .trailers; 1247 } else { 1248 ck.chunk_remaining = size; 1249 ck.state = .data; 1250 } 1251 }, 1252 .data => { 1253 const avail = ck.raw_len - ck.raw_pos; 1254 if (avail == 0) { 1255 return false; 1256 } 1257 const take = @min(avail, ck.chunk_remaining); 1258 const new_body_pos = self.body_pos + take; 1259 1260 if (new_body_pos > self.max_body_size) { 1261 metrics.bodyTooBig(); 1262 return error.BodyTooBig; 1263 } 1264 1265 if (new_body_pos > ck.body_capacity) { 1266 try self.growBody(req_arena, new_body_pos); 1267 } 1268 1269 @memcpy(self.body.?.data[self.body_pos..new_body_pos], ck.raw[ck.raw_pos .. ck.raw_pos + take]); 1270 ck.raw_pos += take; 1271 self.body_pos = new_body_pos; 1272 ck.chunk_remaining -= take; 1273 if (ck.chunk_remaining == 0) { 1274 ck.state = .trailing_crlf; 1275 } 1276 }, 1277 .trailing_crlf => { 1278 if (ck.raw_len - ck.raw_pos < 2) { 1279 return false; 1280 } 1281 if (ck.raw[ck.raw_pos] != '\r' or ck.raw[ck.raw_pos + 1] != '\n') { 1282 return error.InvalidChunkLine; 1283 } 1284 ck.raw_pos += 2; 1285 ck.state = .size_line; 1286 }, 1287 .trailers => { 1288 const slice = ck.raw[ck.raw_pos..ck.raw_len]; 1289 const lf = std.mem.indexOfScalar(u8, slice, '\n') orelse return false; 1290 if (lf == 0 or slice[lf - 1] != '\r') { 1291 return error.InvalidChunkLine; 1292 } 1293 1294 ck.raw_pos += lf + 1; 1295 if (lf == 1) { 1296 // empty line — end of trailers, body fully decoded 1297 self.body_len = self.body_pos; 1298 ck.state = .done; 1299 return true; 1300 } 1301 // non-empty trailer line; discard and keep looking for the 1302 // empty terminator 1303 }, 1304 .done => return true, 1305 } 1306 } 1307 } 1308 1309 fn growBody(self: *State, req_arena: Allocator, needed: usize) !void { 1310 var ck = &self.chunked.?; 1311 const cur = ck.body_capacity; 1312 var new_cap = cur * 2; 1313 1314 if (new_cap < needed) { 1315 new_cap = needed; 1316 } 1317 1318 if (new_cap > self.max_body_size) { 1319 new_cap = self.max_body_size; 1320 } 1321 1322 if (new_cap < needed) { 1323 metrics.bodyTooBig(); 1324 return error.BodyTooBig; 1325 } 1326 1327 const new_data = try req_arena.alloc(u8, new_cap); 1328 @memcpy(new_data[0..self.body_pos], self.body.?.data[0..self.body_pos]); 1329 // releases pool buffers; no-op for arena buffers 1330 self.buffer_pool.release(self.body.?); 1331 self.body = .{ .type = .arena, .data = new_data }; 1332 ck.body_capacity = new_cap; 1333 } 1334}; 1335 1336// Hex digits, optional ';' + extension, then '\r\n'. Generous cap; real-world 1337// values are always tiny. 1338const MAX_CHUNK_SIZE_LINE: usize = 64; 1339 1340fn parseHex(str: []const u8) ?usize { 1341 if (str.len == 0) { 1342 return null; 1343 } 1344 1345 var n: usize = 0; 1346 for (str) |b| { 1347 const d: usize = switch (b) { 1348 '0'...'9' => @intCast(b - '0'), 1349 'a'...'f' => @intCast(b - 'a' + 10), 1350 'A'...'F' => @intCast(b - 'A' + 10), 1351 else => return null, 1352 }; 1353 n = std.math.shlExact(usize, n, 4) catch return null; 1354 n = std.math.add(usize, n, d) catch return null; 1355 } 1356 return n; 1357} 1358 1359// Zig 0.16's Io.net.Stream doesn't expose WouldBlock. It just panics. I don't 1360// understand why it's like that. But we're in a transition, and I just want to 1361// make this work. So, in "real" code, `source` will be a socket_t. In tests, 1362// `source` will be an Io.Reader. 1363// In theory, I woulc wrap the `socket_t` in a `Io.Reader` that behaves like I 1364// want it to, but this is _a lot_ easier, especially since all of this will 1365// be re-worked when networking is fully working in Zig. 1366fn zig016HackRead(source: anytype, buf: []u8) !usize { 1367 if (@TypeOf(source) == posix.socket_t) { 1368 return posix.read(source, buf); 1369 } 1370 // source is a reader 1371 var vecs: [1][]u8 = .{buf}; 1372 return source.readVec(&vecs); 1373} 1374 1375const allowedHeaderValueByte = blk: { 1376 var v = [_]bool{false} ** 256; 1377 for ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_ :;.,/\"'?!(){}[]@<>=-+*#$&`|~^%\t\\") |b| { 1378 v[b] = true; 1379 } 1380 break :blk v; 1381}; 1382 1383inline fn trimLeadingSpaceCount(in: []const u8) struct { []const u8, usize } { 1384 if (in.len > 1 and in[0] == ' ') { 1385 // very common case 1386 const n = in[1]; 1387 if (n != ' ' and n != '\t') { 1388 return .{ in[1..], 1 }; 1389 } 1390 } 1391 1392 for (in, 0..) |b, i| { 1393 if (b != ' ' and b != '\t') return .{ in[i..], i }; 1394 } 1395 return .{ "", in.len }; 1396} 1397 1398inline fn trimLeadingSpace(in: []const u8) []const u8 { 1399 const out, _ = trimLeadingSpaceCount(in); 1400 return out; 1401} 1402 1403fn atoi(str: []const u8) ?usize { 1404 if (str.len == 0) { 1405 return null; 1406 } 1407 1408 var n: usize = 0; 1409 for (str) |b| { 1410 if (b < '0' or b > '9') { 1411 return null; 1412 } 1413 n = std.math.mul(usize, n, 10) catch return null; 1414 n = std.math.add(usize, n, @intCast(b - '0')) catch return null; 1415 } 1416 return n; 1417} 1418 1419const t = @import("t.zig"); 1420test "atoi" { 1421 var buf: [5]u8 = undefined; 1422 for (0..99999) |i| { 1423 var writer = std.Io.Writer.fixed(&buf); 1424 try writer.printInt(i, 10, .lower, .{}); 1425 try t.expectEqual(i, atoi(buf[0..writer.end]).?); 1426 } 1427 1428 try t.expectEqual(null, atoi("")); 1429 try t.expectEqual(null, atoi("392a")); 1430 try t.expectEqual(null, atoi("b392")); 1431 try t.expectEqual(null, atoi("3c92")); 1432} 1433 1434test "allowedHeaderValueByte" { 1435 var all = std.mem.zeroes([255]bool); 1436 for ('a'..('z' + 1)) |b| all[b] = true; 1437 for ('A'..('Z' + 1)) |b| all[b] = true; 1438 for ('0'..('9' + 1)) |b| all[b] = true; 1439 for ([_]u8{ '_', ' ', ',', ':', ';', '.', ',', '\\', '/', '"', '\'', '?', '!', '(', ')', '{', '}', '[', ']', '@', '<', '>', '=', '-', '+', '*', '#', '$', '&', '`', '|', '~', '^', '%', '\t' }) |b| { 1440 all[b] = true; 1441 } 1442 for (128..255) |b| all[b] = false; 1443 1444 for (all, 0..) |allowed, b| { 1445 try t.expectEqual(allowed, allowedHeaderValueByte[@intCast(b)]); 1446 } 1447} 1448 1449test "request: header too big" { 1450 try expectParseError(error.HeaderTooBig, "GET / HTTP/1.1\r\n\r\n", .{ .buffer_size = 17 }); 1451 try expectParseError(error.HeaderTooBig, "GET / HTTP/1.1\r\nH: v\r\n\r\n", .{ .buffer_size = 23 }); 1452} 1453 1454test "request: parse method" { 1455 defer t.reset(); 1456 { 1457 try expectParseError(error.UnknownMethod, " PUT / HTTP/1.1", .{}); 1458 try expectParseError(error.UnknownMethod, "GET/HTTP/1.1", .{}); 1459 try expectParseError(error.UnknownMethod, "PUT/HTTP/1.1", .{}); 1460 1461 try expectParseError(error.UnknownMethod, "get / HTTP/1.1", .{}); // lowecase 1462 try expectParseError(error.UnknownMethod, "Other / HTTP/1.1", .{}); // lowercase 1463 } 1464 1465 { 1466 const r = try testParse("GET / HTTP/1.1\r\n\r\n", .{}); 1467 try t.expectEqual(http.Method.GET, r.method); 1468 try t.expectString("", r.method_string); 1469 } 1470 1471 { 1472 const r = try testParse("PUT / HTTP/1.1\r\n\r\n", .{}); 1473 try t.expectEqual(http.Method.PUT, r.method); 1474 try t.expectString("", r.method_string); 1475 } 1476 1477 { 1478 const r = try testParse("POST / HTTP/1.1\r\n\r\n", .{}); 1479 try t.expectEqual(http.Method.POST, r.method); 1480 try t.expectString("", r.method_string); 1481 } 1482 1483 { 1484 const r = try testParse("HEAD / HTTP/1.1\r\n\r\n", .{}); 1485 try t.expectEqual(http.Method.HEAD, r.method); 1486 try t.expectString("", r.method_string); 1487 } 1488 1489 { 1490 const r = try testParse("PATCH / HTTP/1.1\r\n\r\n", .{}); 1491 try t.expectEqual(http.Method.PATCH, r.method); 1492 try t.expectString("", r.method_string); 1493 } 1494 1495 { 1496 const r = try testParse("DELETE / HTTP/1.1\r\n\r\n", .{}); 1497 try t.expectEqual(http.Method.DELETE, r.method); 1498 try t.expectString("", r.method_string); 1499 } 1500 1501 { 1502 const r = try testParse("OPTIONS / HTTP/1.1\r\n\r\n", .{}); 1503 try t.expectEqual(http.Method.OPTIONS, r.method); 1504 try t.expectString("", r.method_string); 1505 } 1506 1507 { 1508 const r = try testParse("TEA / HTTP/1.1\r\n\r\n", .{}); 1509 try t.expectEqual(http.Method.OTHER, r.method); 1510 try t.expectString("TEA", r.method_string); 1511 } 1512} 1513 1514test "request: parse request target" { 1515 defer t.reset(); 1516 { 1517 try expectParseError(error.InvalidRequestTarget, "GET NOPE", .{}); 1518 try expectParseError(error.InvalidRequestTarget, "GET nope ", .{}); 1519 try expectParseError(error.InvalidRequestTarget, "GET http://www.pondzpondz.com/test ", .{}); // this should be valid 1520 try expectParseError(error.InvalidRequestTarget, "PUT hello ", .{}); 1521 try expectParseError(error.InvalidRequestTarget, "POST /hello ", .{}); 1522 try expectParseError(error.InvalidRequestTarget, "POST *hello ", .{}); 1523 } 1524 1525 { 1526 const r = try testParse("PUT / HTTP/1.1\r\n\r\n", .{}); 1527 try t.expectString("/", r.url.raw); 1528 } 1529 1530 { 1531 const r = try testParse("PUT /api/v2 HTTP/1.1\r\n\r\n", .{}); 1532 try t.expectString("/api/v2", r.url.raw); 1533 } 1534 1535 { 1536 const r = try testParse("DELETE /API/v2?hack=true&over=9000%20!! HTTP/1.1\r\n\r\n", .{}); 1537 try t.expectString("/API/v2?hack=true&over=9000%20!!", r.url.raw); 1538 } 1539 1540 { 1541 const r = try testParse("PUT * HTTP/1.1\r\n\r\n", .{}); 1542 try t.expectString("*", r.url.raw); 1543 } 1544} 1545 1546test "request: parse protocol" { 1547 defer t.reset(); 1548 { 1549 try expectParseError(error.UnknownProtocol, "GET / http/1.1\r\n", .{}); 1550 try expectParseError(error.UnsupportedProtocol, "GET / HTTP/2.0\r\n", .{}); 1551 } 1552 1553 { 1554 const r = try testParse("PUT / HTTP/1.0\r\n\r\n", .{}); 1555 try t.expectEqual(http.Protocol.HTTP10, r.protocol); 1556 } 1557 1558 { 1559 const r = try testParse("PUT / HTTP/1.1\r\n\r\n", .{}); 1560 try t.expectEqual(http.Protocol.HTTP11, r.protocol); 1561 } 1562} 1563 1564test "request: parse headers" { 1565 defer t.reset(); 1566 { 1567 try expectParseError(error.InvalidHeaderLine, "GET / HTTP/1.1\r\nHost\r\n", .{}); 1568 } 1569 1570 { 1571 const r = try testParse("PUT / HTTP/1.0\r\n\r\n", .{}); 1572 try t.expectEqual(0, r.headers.len); 1573 } 1574 1575 { 1576 var r = try testParse("PUT / HTTP/1.0\r\nHost: pondzpondz.com\r\n\r\n", .{}); 1577 1578 try t.expectEqual(1, r.headers.len); 1579 try t.expectString("pondzpondz.com", r.headers.get("host").?); 1580 } 1581 1582 { 1583 var r = try testParse("PUT / HTTP/1.0\r\nHost: pondzpondz.com\r\nMisc: Some-Value\r\nAuthorization:none\r\n\r\n", .{}); 1584 try t.expectEqual(3, r.headers.len); 1585 try t.expectString("pondzpondz.com", r.header("host").?); 1586 try t.expectString("Some-Value", r.header("misc").?); 1587 try t.expectString("none", r.header("authorization").?); 1588 } 1589 1590 { 1591 // RFC 7230 §3.2.4 — leading and trailing OWS (space, tab) is not 1592 // part of the field value. 1593 var r = try testParse("PUT / HTTP/1.0\r\nA: trailing-space \r\nB:\tboth\t \r\nC:no-trim\r\nD: \r\n\r\n", .{}); 1594 try t.expectString("trailing-space", r.header("a").?); 1595 try t.expectString("both", r.header("b").?); 1596 try t.expectString("no-trim", r.header("c").?); 1597 try t.expectString("", r.header("d").?); 1598 } 1599} 1600 1601test "request: canKeepAlive" { 1602 defer t.reset(); 1603 { 1604 // implicitly keepalive for 1.1 1605 var r = try testParse("GET / HTTP/1.1\r\n\r\n", .{}); 1606 try t.expectEqual(true, r.canKeepAlive()); 1607 } 1608 1609 { 1610 // explicitly keepalive for 1.1 1611 var r = try testParse("GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n", .{}); 1612 try t.expectEqual(true, r.canKeepAlive()); 1613 } 1614 1615 { 1616 // explicitly not keepalive for 1.1 1617 var r = try testParse("GET / HTTP/1.1\r\nConnection: close\r\n\r\n", .{}); 1618 try t.expectEqual(false, r.canKeepAlive()); 1619 } 1620} 1621 1622test "request: query" { 1623 defer t.reset(); 1624 { 1625 // none 1626 var r = try testParse("PUT / HTTP/1.1\r\n\r\n", .{}); 1627 try t.expectEqual(0, (try r.query()).len); 1628 } 1629 1630 { 1631 // none with path 1632 var r = try testParse("PUT /why/would/this/matter HTTP/1.1\r\n\r\n", .{}); 1633 try t.expectEqual(0, (try r.query()).len); 1634 } 1635 1636 { 1637 // value-less 1638 var r = try testParse("PUT /?a HTTP/1.1\r\n\r\n", .{}); 1639 const query = try r.query(); 1640 try t.expectEqual(1, query.len); 1641 try t.expectString("", query.get("a").?); 1642 try t.expectEqual(null, query.get("b")); 1643 } 1644 1645 { 1646 // single 1647 var r = try testParse("PUT /?a=1 HTTP/1.1\r\n\r\n", .{}); 1648 const query = try r.query(); 1649 try t.expectEqual(1, query.len); 1650 try t.expectString("1", query.get("a").?); 1651 try t.expectEqual(null, query.get("b")); 1652 } 1653 1654 { 1655 // multiple 1656 var r = try testParse("PUT /path?Teg=Tea&it%20%20IS=over%209000%24&ha%09ck HTTP/1.1\r\n\r\n", .{}); 1657 const query = try r.query(); 1658 try t.expectEqual(3, query.len); 1659 try t.expectString("Tea", query.get("Teg").?); 1660 try t.expectString("over 9000$", query.get("it IS").?); 1661 try t.expectString("", query.get("ha\tck").?); 1662 } 1663} 1664 1665test "request: body content-length" { 1666 defer t.reset(); 1667 { 1668 // too big 1669 try expectParseError(error.BodyTooBig, "POST / HTTP/1.0\r\nContent-Length: 10\r\n\r\nOver 9000!", .{ .max_body_size = 9 }); 1670 } 1671 1672 { 1673 // no body 1674 var r = try testParse("PUT / HTTP/1.0\r\nHost: pondzpondz.com\r\nContent-Length: 0\r\n\r\n", .{ .max_body_size = 10 }); 1675 try t.expectEqual(null, r.body()); 1676 try t.expectEqual(null, r.body()); 1677 } 1678 1679 { 1680 // fits into static buffer 1681 var r = try testParse("POST / HTTP/1.0\r\nContent-Length: 10\r\n\r\nOver 9000!", .{}); 1682 try t.expectString("Over 9000!", r.body().?); 1683 try t.expectString("Over 9000!", r.body().?); 1684 } 1685 1686 { 1687 // Requires dynamic buffer 1688 var r = try testParse("POST / HTTP/1.0\r\nContent-Length: 11\r\n\r\nOver 9001!!", .{ .buffer_size = 40 }); 1689 try t.expectString("Over 9001!!", r.body().?); 1690 try t.expectString("Over 9001!!", r.body().?); 1691 } 1692} 1693 1694// the query and body both (can) occupy space in our static buffer 1695test "request: query & body" { 1696 defer t.reset(); 1697 1698 // query then body 1699 var r = try testParse("POST /?search=keemun%20tea HTTP/1.0\r\nContent-Length: 10\r\n\r\nOver 9000!", .{}); 1700 try t.expectString("keemun tea", (try r.query()).get("search").?); 1701 try t.expectString("Over 9000!", r.body().?); 1702 1703 // results should be cached internally, but let's double check 1704 try t.expectString("keemun tea", (try r.query()).get("search").?); 1705} 1706 1707test "request: invalid content-length" { 1708 defer t.reset(); 1709 try expectParseError(error.InvalidContentLength, "GET / HTTP/1.0\r\nContent-Length: 1\r\n\r\nabc", .{}); 1710 try expectParseError(error.InvalidContentLength, "POST / HTTP/1.1\r\nHost: a\r\nContent-Length: 0\r\nContent-Length: 1\r\n\r\n", .{}); 1711 try expectParseError(error.InvalidContentLength, "POST / HTTP/1.1\r\nHost: a\r\nContent-Length: 0\r\nContent-Length: 0\r\n\r\n", .{}); 1712} 1713 1714test "request: invalid host" { 1715 defer t.reset(); 1716 try expectParseError(error.InvalidHost, "GET / HTTP/1.1\r\nHost: a\r\nHost: b\r\n\r\n", .{}); 1717 try expectParseError(error.InvalidHost, "GET / HTTP/1.1\r\nHost: a\r\nHost: a\r\n\r\n", .{}); 1718} 1719 1720test "request: body chunked" { 1721 defer t.reset(); 1722 { 1723 // simple two-chunk body that fits in initial pooled buffer 1724 var r = try testParse("POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n6\r\n World\r\n0\r\n\r\n", .{}); 1725 try t.expectString("Hello World", r.body().?); 1726 try t.expectString("Hello World", r.body().?); 1727 } 1728 1729 { 1730 // empty body via single 0-sized chunk 1731 var r = try testParse("POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", .{}); 1732 try t.expectString("", r.body().?); 1733 } 1734 1735 { 1736 // chunk size with extension is accepted (extension ignored) 1737 var r = try testParse("POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5;name=value\r\nHello\r\n0\r\n\r\n", .{}); 1738 try t.expectString("Hello", r.body().?); 1739 } 1740 1741 { 1742 // hex size > 9 (uppercase and lowercase) 1743 var r = try testParse("POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\nA\r\n0123456789\r\nb\r\nabcdefghijk\r\n0\r\n\r\n", .{}); 1744 try t.expectString("0123456789abcdefghijk", r.body().?); 1745 } 1746 1747 { 1748 // trailers are accepted and discarded 1749 var r = try testParse("POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n0\r\nX-Trailer: yes\r\nX-Other: 1\r\n\r\n", .{}); 1750 try t.expectString("Hello", r.body().?); 1751 } 1752 1753 { 1754 // case-insensitive transfer-encoding value, surrounding whitespace OK 1755 var r = try testParse("POST / HTTP/1.1\r\nTransfer-Encoding: ChUnKeD \r\n\r\n3\r\nfoo\r\n0\r\n\r\n", .{}); 1756 try t.expectString("foo", r.body().?); 1757 } 1758} 1759 1760test "request: chunked overflows initial pool buffer" { 1761 defer t.reset(); 1762 // pool buffer_size is 256 in test setup; send a chunked body that's larger 1763 // so the grow path runs. 1764 const big_chunk_size = 1000; 1765 const aa = t.arena.allocator(); 1766 const buf = aa.alloc(u8, big_chunk_size) catch unreachable; 1767 for (buf, 0..) |*b, i| b.* = @intCast(('a' + (i % 26))); 1768 1769 const req = std.fmt.allocPrint(aa, "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n{x}\r\n{s}\r\n0\r\n\r\n", .{ big_chunk_size, buf }) catch unreachable; 1770 var r = try testParse(req, .{}); 1771 try t.expectString(buf, r.body().?); 1772} 1773 1774test "request: chunked too big" { 1775 defer t.reset(); 1776 // 11-byte body, max_body_size = 10 1777 try expectParseError( 1778 error.BodyTooBig, 1779 "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n6\r\n World\r\n0\r\n\r\n", 1780 .{ .max_body_size = 10 }, 1781 ); 1782} 1783 1784test "request: invalid transfer-encoding" { 1785 defer t.reset(); 1786 // Non-chunked TE 1787 try expectParseError(error.InvalidTransferEncoding, "POST / HTTP/1.1\r\nTransfer-Encoding: gzip\r\n\r\n", .{}); 1788 // Combined with content-length is rejected 1789 try expectParseError(error.InvalidTransferEncoding, "POST / HTTP/1.1\r\nContent-Length: 5\r\nTransfer-Encoding: chunked\r\n\r\n", .{}); 1790 // Multi-coding with chunked is rejected 1791 try expectParseError(error.InvalidTransferEncoding, "POST / HTTP/1.1\r\nTransfer-Encoding: gzip, chunked\r\n\r\n", .{}); 1792} 1793 1794test "request: invalid chunked framing" { 1795 defer t.reset(); 1796 // bad hex in chunk size 1797 try expectParseError(error.InvalidChunkSize, "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\nZZ\r\nHello\r\n0\r\n\r\n", .{}); 1798 // empty hex 1799 try expectParseError(error.InvalidChunkSize, "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n;ext\r\n0\r\n\r\n", .{}); 1800 // missing trailing \r\n after chunk data 1801 try expectParseError(error.InvalidChunkLine, "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHelloXX0\r\n\r\n", .{}); 1802} 1803 1804test "request: chunked across fragmented reads" { 1805 defer t.reset(); 1806 const aa = t.arena.allocator(); 1807 1808 // Build a chunked body where the body itself exceeds the initial pool 1809 // buffer (256 bytes), forcing a grow, and feed it through the FakeReader 1810 // which fragments reads at random byte boundaries. 1811 const big = 600; 1812 var body = aa.alloc(u8, big) catch unreachable; 1813 for (body, 0..) |*b, i| b.* = @intCast('A' + (i % 26)); 1814 1815 const req = std.fmt.allocPrint( 1816 aa, 1817 // two chunks split, plus a trailer line 1818 "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n{x}\r\n{s}\r\n{x}\r\n{s}\r\n0\r\nX-Note: ok\r\n\r\n", 1819 .{ 200, body[0..200], big - 200, body[200..] }, 1820 ) catch unreachable; 1821 1822 var ctx = t.Context.init(.{ .request = .{ .buffer_size = 128 } }); 1823 ctx.fake = true; 1824 defer ctx.deinit(); 1825 1826 ctx.write(req); 1827 var fake = ctx.fakeReader(); 1828 const fr = &fake.interface; 1829 while (true) { 1830 const done = try ctx.conn.req_state.parse(ctx.conn, fr); 1831 if (done) break; 1832 } 1833 var request = Request.init(ctx.conn.req_arena.allocator(), ctx.conn); 1834 try t.expectString(body, request.body().?); 1835} 1836 1837test "parseHex" { 1838 try t.expectEqual(@as(?usize, 0), parseHex("0")); 1839 try t.expectEqual(@as(?usize, 10), parseHex("a")); 1840 try t.expectEqual(@as(?usize, 10), parseHex("A")); 1841 try t.expectEqual(@as(?usize, 255), parseHex("ff")); 1842 try t.expectEqual(@as(?usize, 255), parseHex("Ff")); 1843 try t.expectEqual(@as(?usize, 0x1A2b), parseHex("1A2b")); 1844 try t.expectEqual(@as(?usize, null), parseHex("")); 1845 try t.expectEqual(@as(?usize, null), parseHex("g")); 1846 try t.expectEqual(@as(?usize, null), parseHex("12g")); 1847} 1848 1849test "body: json" { 1850 defer t.reset(); 1851 const Tea = struct { 1852 type: []const u8, 1853 }; 1854 1855 { 1856 // too big 1857 try expectParseError(error.BodyTooBig, "POST / HTTP/1.0\r\nContent-Length: 17\r\n\r\n{\"type\":\"keemun\"}", .{ .max_body_size = 16 }); 1858 } 1859 1860 { 1861 // no body 1862 var r = try testParse("PUT / HTTP/1.0\r\nHost: pondzpondz.com\r\nContent-Length: 0\r\n\r\n", .{ .max_body_size = 10 }); 1863 try t.expectEqual(null, try r.json(Tea)); 1864 try t.expectEqual(null, try r.json(Tea)); 1865 } 1866 1867 { 1868 // parses json 1869 var r = try testParse("POST / HTTP/1.0\r\nContent-Length: 17\r\n\r\n{\"type\":\"keemun\"}", .{}); 1870 try t.expectString("keemun", (try r.json(Tea)).?.type); 1871 try t.expectString("keemun", (try r.json(Tea)).?.type); 1872 } 1873} 1874 1875test "body: jsonValue" { 1876 defer t.reset(); 1877 { 1878 // too big 1879 try expectParseError(error.BodyTooBig, "POST / HTTP/1.0\r\nContent-Length: 17\r\n\r\n{\"type\":\"keemun\"}", .{ .max_body_size = 16 }); 1880 } 1881 1882 { 1883 // no body 1884 var r = try testParse("PUT / HTTP/1.0\r\nHost: pondzpondz.com\r\nContent-Length: 0\r\n\r\n", .{ .max_body_size = 10 }); 1885 try t.expectEqual(null, try r.jsonValue()); 1886 try t.expectEqual(null, try r.jsonValue()); 1887 } 1888 1889 { 1890 // parses json 1891 var r = try testParse("POST / HTTP/1.0\r\nContent-Length: 17\r\n\r\n{\"type\":\"keemun\"}", .{}); 1892 try t.expectString("keemun", (try r.jsonValue()).?.object.get("type").?.string); 1893 try t.expectString("keemun", (try r.jsonValue()).?.object.get("type").?.string); 1894 } 1895} 1896 1897test "body: jsonObject" { 1898 defer t.reset(); 1899 { 1900 // too big 1901 try expectParseError(error.BodyTooBig, "POST / HTTP/1.0\r\nContent-Length: 17\r\n\r\n{\"type\":\"keemun\"}", .{ .max_body_size = 16 }); 1902 } 1903 1904 { 1905 // no body 1906 var r = try testParse("PUT / HTTP/1.0\r\nHost: pondzpondz.com\r\nContent-Length: 0\r\n\r\n", .{ .max_body_size = 10 }); 1907 try t.expectEqual(null, try r.jsonObject()); 1908 try t.expectEqual(null, try r.jsonObject()); 1909 } 1910 1911 { 1912 // not an object 1913 var r = try testParse("POST / HTTP/1.0\r\nContent-Length: 7\r\n\r\n\"hello\"", .{}); 1914 try t.expectEqual(null, try r.jsonObject()); 1915 try t.expectEqual(null, try r.jsonObject()); 1916 } 1917 1918 { 1919 // parses json 1920 var r = try testParse("POST / HTTP/1.0\r\nContent-Length: 17\r\n\r\n{\"type\":\"keemun\"}", .{}); 1921 try t.expectString("keemun", (try r.jsonObject()).?.get("type").?.string); 1922 try t.expectString("keemun", (try r.jsonObject()).?.get("type").?.string); 1923 } 1924} 1925 1926test "body: formData" { 1927 defer t.reset(); 1928 { 1929 // too big 1930 try expectParseError(error.BodyTooBig, "POST / HTTP/1.0\r\nContent-Length: 22\r\n\r\nname=test", .{ .max_body_size = 21 }); 1931 } 1932 1933 { 1934 // no body 1935 var r = try testParse("POST / HTTP/1.0\r\n\r\nContent-Length: 0\r\n\r\n", .{ .max_body_size = 10 }); 1936 const formData = try r.formData(); 1937 try t.expectEqual(null, formData.get("name")); 1938 try t.expectEqual(null, formData.get("name")); 1939 } 1940 1941 { 1942 // parses formData 1943 var r = try testParse("POST / HTTP/1.0\r\nContent-Length: 9\r\n\r\nname=test", .{ .max_form_count = 2 }); 1944 const formData = try r.formData(); 1945 try t.expectString("test", formData.get("name").?); 1946 try t.expectString("test", formData.get("name").?); 1947 } 1948 1949 { 1950 // multiple inputs 1951 var r = try testParse("POST / HTTP/1.0\r\nContent-Length: 25\r\n\r\nname=test1&password=test2", .{ .max_form_count = 2 }); 1952 1953 const formData = try r.formData(); 1954 try t.expectString("test1", formData.get("name").?); 1955 try t.expectString("test1", formData.get("name").?); 1956 1957 try t.expectString("test2", formData.get("password").?); 1958 try t.expectString("test2", formData.get("password").?); 1959 } 1960 1961 { 1962 // test decoding 1963 var r = try testParse("POST / HTTP/1.0\r\nContent-Length: 44\r\n\r\ntest=%21%40%23%24%25%5E%26*%29%28-%3D%2B%7C+", .{ .max_form_count = 2 }); 1964 1965 const formData = try r.formData(); 1966 try t.expectString("!@#$%^&*)(-=+| ", formData.get("test").?); 1967 try t.expectString("!@#$%^&*)(-=+| ", formData.get("test").?); 1968 } 1969} 1970 1971test "body: multiFormData valid" { 1972 defer t.reset(); 1973 1974 { 1975 // no body 1976 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=BX1" }, &.{}), .{ .max_multiform_count = 5 }); 1977 const formData = try r.multiFormData(); 1978 try t.expectEqual(0, formData.len); 1979 try t.expectString("multipart/form-data; boundary=BX1", r.header("content-type").?); 1980 } 1981 1982 { 1983 // parses single field 1984 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=-90x" }, &.{ "---90x\r\n", "Content-Disposition: form-data; name=\"description\"\r\n\r\n", "the-desc\r\n", "---90x--\r\n" }), .{ .max_multiform_count = 5 }); 1985 1986 const formData = try r.multiFormData(); 1987 try t.expectString("the-desc", formData.get("description").?.value); 1988 try t.expectString("the-desc", formData.get("description").?.value); 1989 } 1990 1991 { 1992 // parses single field with filename 1993 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=-90x" }, &.{ "---90x\r\n", "Content-Disposition: form-data; filename=\"file1.zig\"; name=file\r\n\r\n", "some binary data\r\n", "---90x--\r\n" }), .{ .max_multiform_count = 5 }); 1994 1995 const formData = try r.multiFormData(); 1996 const field = formData.get("file").?; 1997 try t.expectString("some binary data", field.value); 1998 try t.expectString("file1.zig", field.filename.?); 1999 } 2000 2001 { 2002 // quoted boundary 2003 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=\"-90x\"" }, &.{ "---90x\r\n", "Content-Disposition: form-data; name=\"description\"\r\n\r\n", "the-desc\r\n", "---90x--\r\n" }), .{ .max_multiform_count = 5 }); 2004 2005 const formData = try r.multiFormData(); 2006 const field = formData.get("description").?; 2007 try t.expectEqual(null, field.filename); 2008 try t.expectString("the-desc", field.value); 2009 } 2010 2011 { 2012 // multiple fields 2013 var r = try testParse(buildRequest(&.{ "GET /something HTTP/1.1", "Content-Type: multipart/form-data; boundary=----99900AB" }, &.{ "------99900AB\r\n", "content-type: text/plain; charset=utf-8\r\n", "content-disposition: form-data; name=\"fie\\\" \\?l\\d\"\r\n\r\n", "Value - 1\r\n", "------99900AB\r\n", "Content-Disposition: form-data; filename=another.zip; name=field2\r\n\r\n", "Value - 2\r\n", "------99900AB--\r\n" }), .{ .max_multiform_count = 5 }); 2014 2015 const formData = try r.multiFormData(); 2016 try t.expectEqual(2, formData.len); 2017 2018 const field1 = formData.get("fie\" ?l\\d").?; 2019 try t.expectEqual(null, field1.filename); 2020 try t.expectString("Value - 1", field1.value); 2021 2022 const field2 = formData.get("field2").?; 2023 try t.expectString("Value - 2", field2.value); 2024 try t.expectString("another.zip", field2.filename.?); 2025 } 2026 2027 { 2028 // enforce limit 2029 var r = try testParse(buildRequest(&.{ "GET /something HTTP/1.1", "Content-Type: multipart/form-data; boundary=----99900AB" }, &.{ "------99900AB\r\n", "Content-Type: text/plain; charset=utf-8\r\n", "Content-Disposition: form-data; name=\"fie\\\" \\?l\\d\"\r\n\r\n", "Value - 1\r\n", "------99900AB\r\n", "Content-Disposition: form-data; filename=another; name=field2\r\n\r\n", "Value - 2\r\n", "------99900AB--\r\n" }), .{ .max_multiform_count = 1 }); 2030 2031 defer t.reset(); 2032 const formData = try r.multiFormData(); 2033 try t.expectEqual(1, formData.len); 2034 try t.expectString("Value - 1", formData.get("fie\" ?l\\d").?.value); 2035 } 2036} 2037 2038test "body: multiFormData invalid" { 2039 defer t.reset(); 2040 { 2041 // large boundary 2042 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=12345678901234567890123456789012345678901234567890123456789012345678901" }, &.{"garbage"}), .{}); 2043 try t.expectError(error.InvalidMultiPartFormDataHeader, r.multiFormData()); 2044 } 2045 2046 { 2047 // no closing quote 2048 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=\"123" }, &.{"garbage"}), .{}); 2049 try t.expectError(error.InvalidMultiPartFormDataHeader, r.multiFormData()); 2050 } 2051 2052 { 2053 // no content-dispotion field header 2054 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=-90x" }, &.{ "---90x\r\n", "the-desc\r\n", "---90x--\r\n" }), .{ .max_multiform_count = 5 }); 2055 try t.expectError(error.InvalidMultiPartEncoding, r.multiFormData()); 2056 } 2057 2058 { 2059 // no content dispotion naem 2060 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=-90x" }, &.{ "---90x\r\n", "Content-Disposition: form-data; x=a", "the-desc\r\n", "---90x--\r\n" }), .{ .max_multiform_count = 5 }); 2061 try t.expectError(error.InvalidMultiPartEncoding, r.multiFormData()); 2062 } 2063 2064 { 2065 // missing name end quote 2066 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=-90x" }, &.{ "---90x\r\n", "Content-Disposition: form-data; name=\"hello\r\n\r\n", "the-desc\r\n", "---90x--\r\n" }), .{ .max_multiform_count = 5 }); 2067 try t.expectError(error.InvalidMultiPartEncoding, r.multiFormData()); 2068 } 2069 2070 { 2071 // missing missing newline 2072 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=-90x" }, &.{ "---90x\r\n", "Content-Disposition: form-data; name=hello\r\n", "the-desc\r\n", "---90x--\r\n" }), .{ .max_multiform_count = 5 }); 2073 try t.expectError(error.InvalidMultiPartEncoding, r.multiFormData()); 2074 } 2075 2076 { 2077 // missing missing newline x2 2078 var r = try testParse(buildRequest(&.{ "POST / HTTP/1.0", "Content-Type: multipart/form-data; boundary=-90x" }, &.{ "---90x\r\n", "Content-Disposition: form-data; name=hello", "the-desc\r\n", "---90x--\r\n" }), .{ .max_multiform_count = 5 }); 2079 try t.expectError(error.InvalidMultiPartEncoding, r.multiFormData()); 2080 } 2081} 2082 2083test "request: fuzz" { 2084 // We have a bunch of data to allocate for testing, like header names and 2085 // values. Easier to use this arena and reset it after each test run. 2086 const aa = t.arena.allocator(); 2087 defer t.reset(); 2088 2089 var r = t.getRandom(); 2090 const random = r.random(); 2091 for (0..1000) |_| { 2092 // important to test with different buffer sizes, since there's a lot of 2093 // special handling for different cases (e.g the buffer is full and has 2094 // some of the body in it, so we need to copy that to a dynamically allocated 2095 // buffer) 2096 const buffer_size = random.uintAtMost(u16, 1024) + 1024; 2097 2098 var ctx = t.Context.init(.{ 2099 .request = .{ .buffer_size = buffer_size }, 2100 }); 2101 2102 // enable fake mode, we don't go through a real socket, instead we go 2103 // through a fake one, that can simulate having data spread across multiple 2104 // calls to read() 2105 ctx.fake = true; 2106 defer ctx.deinit(); 2107 2108 // how many requests should we make on this 1 individual socket (simulating 2109 // keepalive AND the request pool) 2110 const number_of_requests = random.uintAtMost(u8, 10) + 1; 2111 2112 for (0..number_of_requests) |_| { 2113 defer ctx.conn.requestDone(4096, true) catch unreachable; 2114 const method = randomMethod(random); 2115 const url = t.randomString(random, aa, 20); 2116 2117 ctx.write(method); 2118 ctx.write(" /"); 2119 ctx.write(url); 2120 2121 const number_of_qs = random.uintAtMost(u8, 4); 2122 if (number_of_qs != 0) { 2123 ctx.write("?"); 2124 } 2125 2126 var query = std.StringHashMap([]const u8).init(aa); 2127 for (0..number_of_qs) |_| { 2128 const key = t.randomString(random, aa, 20); 2129 const value = t.randomString(random, aa, 20); 2130 if (!query.contains(key)) { 2131 // TODO: figure out how we want to handle duplicate query values 2132 // (the spec doesn't specify what to do) 2133 query.put(key, value) catch unreachable; 2134 ctx.write(key); 2135 ctx.write("="); 2136 ctx.write(value); 2137 ctx.write("&"); 2138 } 2139 } 2140 2141 ctx.write(" HTTP/1.1\r\n"); 2142 2143 var headers = std.StringHashMap([]const u8).init(aa); 2144 for (0..random.uintAtMost(u8, 4)) |_| { 2145 const name = t.randomString(random, aa, 20); 2146 const value = t.randomString(random, aa, 20); 2147 if (!headers.contains(name)) { 2148 // TODO: figure out how we want to handle duplicate query values 2149 // Note, the spec says we should merge these! 2150 headers.put(name, value) catch unreachable; 2151 ctx.write(name); 2152 ctx.write(": "); 2153 ctx.write(value); 2154 ctx.write("\r\n"); 2155 } 2156 } 2157 2158 var body: ?[]u8 = null; 2159 if (random.uintAtMost(u8, 4) == 0) { 2160 ctx.write("\r\n"); // no body 2161 } else { 2162 body = t.randomString(random, aa, 8000); 2163 const cl = std.fmt.allocPrint(aa, "{d}", .{body.?.len}) catch unreachable; 2164 headers.put("content-length", cl) catch unreachable; 2165 ctx.write("content-length: "); 2166 ctx.write(cl); 2167 ctx.write("\r\n\r\n"); 2168 ctx.write(body.?); 2169 } 2170 2171 var conn = ctx.conn; 2172 var fake_reader = ctx.fakeReader(); 2173 const fr = &fake_reader.interface; 2174 while (true) { 2175 const done = try conn.req_state.parse(conn, fr); 2176 if (done) break; 2177 } 2178 2179 var request = Request.init(conn.req_arena.allocator(), conn); 2180 2181 // assert the headers 2182 var it = headers.iterator(); 2183 while (it.next()) |entry| { 2184 try t.expectString(entry.value_ptr.*, request.header(entry.key_ptr.*).?); 2185 } 2186 2187 // assert the querystring 2188 var actualQuery = request.query() catch unreachable; 2189 it = query.iterator(); 2190 while (it.next()) |entry| { 2191 try t.expectString(entry.value_ptr.*, actualQuery.get(entry.key_ptr.*).?); 2192 } 2193 2194 const actual_body = request.body(); 2195 if (body) |b| { 2196 try t.expectString(b, actual_body.?); 2197 } else { 2198 try t.expectEqual(null, actual_body); 2199 } 2200 } 2201 } 2202} 2203 2204test "request: cookie" { 2205 defer t.reset(); 2206 { 2207 const r = try testParse("PUT / HTTP/1.0\r\n\r\n", .{}); 2208 const cookies = r.cookies(); 2209 try t.expectEqual(null, cookies.get("")); 2210 try t.expectEqual(null, cookies.get("auth")); 2211 } 2212 2213 { 2214 const r = try testParse("PUT / HTTP/1.0\r\nCookie: \r\n\r\n", .{}); 2215 const cookies = r.cookies(); 2216 try t.expectEqual(null, cookies.get("")); 2217 try t.expectEqual(null, cookies.get("auth")); 2218 } 2219 2220 { 2221 const r = try testParse("PUT / HTTP/1.0\r\nCookie: auth=hello\r\n\r\n", .{}); 2222 const cookies = r.cookies(); 2223 try t.expectEqual(null, cookies.get("")); 2224 try t.expectString("hello", cookies.get("auth").?); 2225 try t.expectEqual(null, cookies.get("world")); 2226 } 2227 2228 { 2229 const r = try testParse("PUT / HTTP/1.0\r\nCookie: Name=leto; power=9000\r\n\r\n", .{}); 2230 const cookies = r.cookies(); 2231 try t.expectEqual(null, cookies.get("")); 2232 try t.expectEqual(null, cookies.get("name")); 2233 try t.expectString("leto", cookies.get("Name").?); 2234 try t.expectString("9000", cookies.get("power").?); 2235 } 2236 2237 { 2238 const r = try testParse("PUT / HTTP/1.0\r\nCookie: Name=Ghanima;id=Name\r\n\r\n", .{}); 2239 const cookies = r.cookies(); 2240 try t.expectEqual(null, cookies.get("")); 2241 try t.expectEqual(null, cookies.get("name")); 2242 try t.expectString("Ghanima", cookies.get("Name").?); 2243 try t.expectString("Name", cookies.get("id").?); 2244 } 2245} 2246 2247fn testParse(input: []const u8, config: Config) !Request { 2248 var ctx = t.Context.allocInit(t.arena.allocator(), .{ .request = config }); 2249 ctx.write(input); 2250 var reader = ctx.stream.reader(t.io, &.{}); 2251 const r = &reader.interface; 2252 while (true) { 2253 const done = try ctx.conn.req_state.parse(ctx.conn, r); 2254 if (done) break; 2255 } 2256 return Request.init(ctx.conn.req_arena.allocator(), ctx.conn); 2257} 2258 2259fn expectParseError(expected: anyerror, input: []const u8, config: Config) !void { 2260 var ctx = t.Context.init(.{ .request = config }); 2261 defer ctx.deinit(); 2262 2263 var reader = ctx.stream.reader(t.io, &.{}); 2264 const r = &reader.interface; 2265 ctx.write(input); 2266 try t.expectError(expected, ctx.conn.req_state.parse(ctx.conn, r)); 2267} 2268 2269fn randomMethod(random: std.Random) []const u8 { 2270 return switch (random.uintAtMost(usize, 6)) { 2271 0 => "GET", 2272 1 => "PUT", 2273 2 => "POST", 2274 3 => "PATCH", 2275 4 => "DELETE", 2276 5 => "OPTIONS", 2277 6 => "HEAD", 2278 else => unreachable, 2279 }; 2280} 2281 2282fn buildRequest(header: []const []const u8, body: []const []const u8) []const u8 { 2283 var header_len: usize = 0; 2284 for (header) |h| { 2285 header_len += h.len; 2286 } 2287 2288 var body_len: usize = 0; 2289 for (body) |b| { 2290 body_len += b.len; 2291 } 2292 2293 var aw: std.Io.Writer.Allocating = .init(t.arena.allocator()); 2294 // 100 for the Content-Length that we'll add and all the \r\n 2295 aw.ensureTotalCapacity(header_len + body_len + 100) catch unreachable; 2296 2297 for (header) |h| { 2298 aw.writer.writeAll(h) catch unreachable; 2299 aw.writer.writeAll("\r\n") catch unreachable; 2300 } 2301 aw.writer.writeAll("Content-Length: ") catch unreachable; 2302 aw.writer.print("{d}", .{body_len}) catch unreachable; 2303 aw.writer.writeAll("\r\n\r\n") catch unreachable; 2304 2305 for (body) |b| { 2306 aw.writer.writeAll(b) catch unreachable; 2307 } 2308 2309 return aw.written(); 2310}