unlimited-download.zig
· 3.8 KiB · Zig
Brut
const std = @import("std");
const eql = std.mem.eql;
pub const std_options = .{
.log_level = .info,
};
fn http_error(writer: std.net.Stream.Writer, status_code: u16) !void {
const status = switch (status_code) {
200 => "OK",
400 => "Bad Request",
404 => "Not Found",
405 => "Method Not Allowed",
else => @panic("Invalid status"),
};
try writer.print("HTTP/1.1 {d} {s}\r\nServer: zig\r\nContent-Type: text/plain\r\nContent-Length: {d}\r\n\r\n{s}", .{ status_code, status, status.len, status });
}
fn format_bytes(buf: []u8, bytes: usize) ![]u8 {
const fbytes: f32 = @floatFromInt(bytes);
return switch (bytes) {
0...1024 => std.fmt.bufPrint(buf, "{d} B", .{bytes}),
1025...(1024 * 1024) => std.fmt.bufPrint(buf, "{d:.1} KiB", .{fbytes / 1024}),
(1024 * 1024 + 1)...(1024 * 1024 * 1024) => std.fmt.bufPrint(buf, "{d:.1} MiB", .{fbytes / 1024 / 1024}),
else => std.fmt.bufPrint(buf, "{d:.1} GiB", .{fbytes / 1024 / 1024 / 1024}),
};
}
fn handle_connection(connection: std.net.Server.Connection, path: []u8, filename: []u8) !void {
const writer = connection.stream.writer();
{
var buffer: [255]u8 = undefined;
const n = try connection.stream.read(&buffer);
if (n < 12) {
return http_error(writer, 400);
}
if (!eql(u8, buffer[0..4], "GET ")) {
return http_error(writer, 405);
}
if (n < (11 + path.len) or !eql(u8, buffer[4 .. 4 + path.len], path)) {
return http_error(writer, 404);
}
}
var prng = std.rand.DefaultPrng.init(blk: {
var seed: u64 = undefined;
try std.posix.getrandom(std.mem.asBytes(&seed));
break :blk seed;
});
const random = prng.random();
std.log.info("Client {} connected", .{connection.address});
defer {
connection.stream.close();
std.log.info("Client {} disconnected", .{connection.address});
}
try writer.print("HTTP/1.1 200 OK\r\nServer: zig\r\nContent-Type: application/octet-stream\r\nContent-Disposition: attachment; filename=\"{s}\"\r\n\r\n", .{filename});
var downloaded: usize = 0;
var out_counter: usize = 0;
while (true) {
var writeBuffer: [1024]u8 = undefined;
random.bytes(&writeBuffer);
const wrote = connection.stream.write(&writeBuffer) catch |err| switch (err) {
error.BrokenPipe => break,
error.ConnectionResetByPeer => break,
else => return err,
};
downloaded += wrote;
out_counter += wrote;
if (out_counter >= 1024 * 1024 * 100) {
var buf: [16]u8 = undefined;
const downloaded_fmt = try format_bytes(&buf, downloaded);
std.log.info("Client {} downloaded {s}", .{ connection.address, downloaded_fmt });
out_counter = 0;
}
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer {
const check = gpa.deinit();
if (check == .leak) @panic("memory leaks");
}
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
if (args.len != 5) {
const writer = std.io.getStdErr().writer();
try writer.print("Usage: {s} DOWNLOAD_PATH DOWNLOAD_FILENAME ADDRESS PORT\n", .{args[0]});
std.process.exit(1);
unreachable;
}
const listen_address = try std.net.Address.parseIp(args[3], try std.fmt.parseInt(u16, args[4], 10));
var server = try listen_address.listen(.{ .reuse_address = true });
defer server.deinit();
std.log.info("Listening on http://{s}:{s}", .{ args[3], args[4] });
while (true) {
const connection = try server.accept();
_ = try std.Thread.spawn(.{}, handle_connection, .{ connection, args[1], args[2] });
}
}
| 1 | const std = @import("std"); |
| 2 | const eql = std.mem.eql; |
| 3 | |
| 4 | pub const std_options = .{ |
| 5 | .log_level = .info, |
| 6 | }; |
| 7 | |
| 8 | fn http_error(writer: std.net.Stream.Writer, status_code: u16) !void { |
| 9 | const status = switch (status_code) { |
| 10 | 200 => "OK", |
| 11 | 400 => "Bad Request", |
| 12 | 404 => "Not Found", |
| 13 | 405 => "Method Not Allowed", |
| 14 | else => @panic("Invalid status"), |
| 15 | }; |
| 16 | |
| 17 | try writer.print("HTTP/1.1 {d} {s}\r\nServer: zig\r\nContent-Type: text/plain\r\nContent-Length: {d}\r\n\r\n{s}", .{ status_code, status, status.len, status }); |
| 18 | } |
| 19 | |
| 20 | fn format_bytes(buf: []u8, bytes: usize) ![]u8 { |
| 21 | const fbytes: f32 = @floatFromInt(bytes); |
| 22 | return switch (bytes) { |
| 23 | 0...1024 => std.fmt.bufPrint(buf, "{d} B", .{bytes}), |
| 24 | 1025...(1024 * 1024) => std.fmt.bufPrint(buf, "{d:.1} KiB", .{fbytes / 1024}), |
| 25 | (1024 * 1024 + 1)...(1024 * 1024 * 1024) => std.fmt.bufPrint(buf, "{d:.1} MiB", .{fbytes / 1024 / 1024}), |
| 26 | else => std.fmt.bufPrint(buf, "{d:.1} GiB", .{fbytes / 1024 / 1024 / 1024}), |
| 27 | }; |
| 28 | } |
| 29 | |
| 30 | fn handle_connection(connection: std.net.Server.Connection, path: []u8, filename: []u8) !void { |
| 31 | const writer = connection.stream.writer(); |
| 32 | |
| 33 | { |
| 34 | var buffer: [255]u8 = undefined; |
| 35 | const n = try connection.stream.read(&buffer); |
| 36 | if (n < 12) { |
| 37 | return http_error(writer, 400); |
| 38 | } |
| 39 | |
| 40 | if (!eql(u8, buffer[0..4], "GET ")) { |
| 41 | return http_error(writer, 405); |
| 42 | } |
| 43 | |
| 44 | if (n < (11 + path.len) or !eql(u8, buffer[4 .. 4 + path.len], path)) { |
| 45 | return http_error(writer, 404); |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | var prng = std.rand.DefaultPrng.init(blk: { |
| 50 | var seed: u64 = undefined; |
| 51 | try std.posix.getrandom(std.mem.asBytes(&seed)); |
| 52 | break :blk seed; |
| 53 | }); |
| 54 | const random = prng.random(); |
| 55 | |
| 56 | std.log.info("Client {} connected", .{connection.address}); |
| 57 | defer { |
| 58 | connection.stream.close(); |
| 59 | std.log.info("Client {} disconnected", .{connection.address}); |
| 60 | } |
| 61 | |
| 62 | try writer.print("HTTP/1.1 200 OK\r\nServer: zig\r\nContent-Type: application/octet-stream\r\nContent-Disposition: attachment; filename=\"{s}\"\r\n\r\n", .{filename}); |
| 63 | |
| 64 | var downloaded: usize = 0; |
| 65 | var out_counter: usize = 0; |
| 66 | |
| 67 | while (true) { |
| 68 | var writeBuffer: [1024]u8 = undefined; |
| 69 | random.bytes(&writeBuffer); |
| 70 | const wrote = connection.stream.write(&writeBuffer) catch |err| switch (err) { |
| 71 | error.BrokenPipe => break, |
| 72 | error.ConnectionResetByPeer => break, |
| 73 | else => return err, |
| 74 | }; |
| 75 | downloaded += wrote; |
| 76 | out_counter += wrote; |
| 77 | |
| 78 | if (out_counter >= 1024 * 1024 * 100) { |
| 79 | var buf: [16]u8 = undefined; |
| 80 | const downloaded_fmt = try format_bytes(&buf, downloaded); |
| 81 | std.log.info("Client {} downloaded {s}", .{ connection.address, downloaded_fmt }); |
| 82 | out_counter = 0; |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | pub fn main() !void { |
| 88 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 89 | const allocator = gpa.allocator(); |
| 90 | defer { |
| 91 | const check = gpa.deinit(); |
| 92 | if (check == .leak) @panic("memory leaks"); |
| 93 | } |
| 94 | |
| 95 | const args = try std.process.argsAlloc(allocator); |
| 96 | defer std.process.argsFree(allocator, args); |
| 97 | |
| 98 | if (args.len != 5) { |
| 99 | const writer = std.io.getStdErr().writer(); |
| 100 | try writer.print("Usage: {s} DOWNLOAD_PATH DOWNLOAD_FILENAME ADDRESS PORT\n", .{args[0]}); |
| 101 | std.process.exit(1); |
| 102 | unreachable; |
| 103 | } |
| 104 | |
| 105 | const listen_address = try std.net.Address.parseIp(args[3], try std.fmt.parseInt(u16, args[4], 10)); |
| 106 | var server = try listen_address.listen(.{ .reuse_address = true }); |
| 107 | defer server.deinit(); |
| 108 | std.log.info("Listening on http://{s}:{s}", .{ args[3], args[4] }); |
| 109 | |
| 110 | while (true) { |
| 111 | const connection = try server.accept(); |
| 112 | _ = try std.Thread.spawn(.{}, handle_connection, .{ connection, args[1], args[2] }); |
| 113 | } |
| 114 | } |
| 115 |