╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │66 if (self.owned_path) |path| allocator.free(path); │ │67 } │ │68 }; │ │69 + │ │70 +// the "owner:name" identity a repo url base names, or null for any other base │ │71 +fn repoBaseIdentity(repo_base: []const u8) ?[]const u8 { │ │72 + if (repo_base.len < 2 or repo_base[0] != '/') return null; │ │73 + const identity = repo_base[1..]; │ │74 + if (std.mem.indexOfScalar(u8, identity, ':') == null or std.mem.indexOfAny(u8, identity, "/+") != null) return null;│ │75 + return identity; │ │76 +} │ │77 + │ │78 +// the route a url names under this host's url scheme │ │79 +fn routeFromUrl(host: Host, path: []const u8) ?ui.RoutablePage { │ │80 + return switch (host) { │ │81 + .server => ui.RoutablePage.fromUrl(path), │ │82 + .local => ui.RoutablePage.fromUrlLocal(path), │ │83 + }; │ │84 +} │ │85 │ │86 fn requestRepoSource(io: std.Io, allocator: std.mem.Allocator, host: Host, repo_base: []const u8) !?RequestRepoSource { │ │87 return switch (host) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │88 .server => |server| blk: { │ │73 - const repo_prefix = "/repo/"; │ │74 - if (!std.mem.startsWith(u8, repo_base, repo_prefix)) break :blk null; │ │75 - const path = switch (try serve_common.resolveRepoPath(io, allocator, server.users_dir, server.admin_repo_path, repo_base[repo_prefix.len..], false)) {│ │89 + const identity = repoBaseIdentity(repo_base) orelse break :blk null; │ │90 + const path = switch (try serve_common.resolveRepoPath(io, allocator, server.users_dir, server.admin_repo_path, identity, false)) { │ │91 .ok => |value| value, │ │92 .invalid, .not_found => break :blk null, │ │93 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │228 // before the routable pages │ │229 if (attachmentRequest(path)) |attachment| return serveAttachment(io, request, allocator, attachment, host);│ │230 │ │216 - const route_maybe = switch (host) { │ │217 - .server => ui.RoutablePage.fromUrl(path), │ │218 - .local => ui.RoutablePage.fromUrlLocal(path), │ │219 - }; │ │220 - // a path that's neither a page nor an embed gets the not-found page │ │221 - if (route_maybe == null) if (findEmbed(path)) |embed| { │ │231 + // embeds are matched before the pages so an embed's name never reads as a user │ │232 + if (findEmbed(path)) |embed| { │ │233 // the embeds change with every build, so the browser must revalidate │ │234 // rather than heuristically cache them across server restarts │ │235 try request.respond(embed.body, .{ │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │239 }, │ │240 }); │ │241 return; │ │231 - }; │ │242 + } │ │243 { │ │233 - const current_page = route_maybe orelse .not_found; │ │244 + // a path that's neither a page nor an embed gets the not-found page │ │245 + const current_page = routeFromUrl(host, path) orelse .not_found; │ │246 // resolve the haxy_session cookie's token to a user_id via the │ │247 // store. user_id_buf lives on the stack for the rest of handleRequest,│ │248 // which is plenty for renderIndexHtml to consume. local mode has no │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │500 const authorization: ui.Authorization = blk: { │ │501 const user_id = requestUserId(request, server.session_store) orelse break :blk .login_required; │ │502 │ │491 - const repo_prefix = "/repo/"; │ │492 - if (!std.mem.startsWith(u8, repo_base, repo_prefix)) break :blk .repo_not_found; │ │503 + const identity = repoBaseIdentity(repo_base) orelse break :blk .repo_not_found; │ │504 │ │505 var admin_repo = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = server.admin_repo_path });│ │506 defer admin_repo.deinit(io, allocator); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │507 const moment = try evt.currentMoment(evt.admin_repo_opts, &admin_repo); │ │497 - break :blk try ui.authorizeUser(io, moment, arena, server.users_dir, user_id, repo_base[repo_prefix.len..], min_role);│ │508 + break :blk try ui.authorizeUser(io, moment, arena, server.users_dir, user_id, identity, min_role); │ │509 }; │ │510 switch (authorization) { │ │511 .actor => |actor| return actor, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │529 .server => |server| server, │ │530 .local => return true, │ │531 }; │ │521 - const repo_prefix = "/repo/"; │ │522 - if (!std.mem.startsWith(u8, repo_base, repo_prefix)) return false; │ │532 + const identity = repoBaseIdentity(repo_base) orelse return false; │ │533 │ │534 var admin_repo = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = server.admin_repo_path });│ │535 defer admin_repo.deinit(io, allocator); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │540 const id = requestUserId(request, server.session_store) orelse break :blk null; │ │541 break :blk if (try ui.activeUser(moment, &arena, id) != null) id else null; │ │542 }; │ │533 - const owner_repo = evt.parseOwnerRepoPath(repo_base[repo_prefix.len..]) orelse return false; │ │543 + const owner_repo = evt.parseOwnerRepoPath(identity) orelse return false; │ │544 const repo = (try evt.readRepoByOwnerAndName(io, allocator, &arena, moment, server.users_dir, owner_repo.owner, owner_repo.name, user_id)) orelse return false;│ │545 return repo.role != .none; │ │546 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │588 base: []const u8, │ │589 host: Host, │ │590 ) !void { │ │581 - if (commentBaseParts(base) != null) return handleCommentNew(io, request, allocator, base, host); │ │591 + if (commentBaseParts(host, base) != null) return handleCommentNew(io, request, allocator, base, host);│ │592 │ │593 const issues_suffix = "/issues"; │ │594 if (std.mem.endsWith(u8, base, issues_suffix)) │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │651 } } }); │ │652 }; │ │653 │ │644 - const location = try std.fmt.allocPrint(allocator, "/repo/{s}/{s}", .{ user.event.name, name });│ │654 + const location = try std.fmt.allocPrint(allocator, "/{s}:{s}", .{ user.event.name, name }); │ │655 defer allocator.free(location); │ │656 try request.respond("", .{ │ │657 .status = .see_other, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯