/// <summary> /// Handles a request.</summary> /// <param name="request"> /// Request to handle.</param> /// <param name="setUsername"> /// Action to call when login is successful. Typically used to set the username in a session.</param> /// <param name="loggedInUser"> /// Username of the user currently logged in (typically read from a session).</param> public HttpResponse Handle(HttpRequest request, string loggedInUser, Action<string> setUsername) { return new UrlResolver( new UrlMapping(path: "/login", handler: req => loginHandler(req, setUsername)), new UrlMapping(path: "/changepassword", handler: req => changePasswordHandler(req, loggedInUser)), new UrlMapping(path: "/createuser", handler: req => createUserHandler(req, loggedInUser)), new UrlMapping(path: "/logout", handler: req => logoutHandler(req, setUsername)) ).Handle(request); }
/// <summary>Returns an <see cref="HttpResponse"/> that handles the specified request, either by delivering a file from the local file system, /// or by listing the contents of a directory in the local file system. The file or directory served is determined from the configured /// <see cref="BaseDirectory"/> and the <see cref="HttpRequest.Url"/> of the specified <paramref name="request"/>.</summary> /// <param name="request">HTTP request from the client.</param> /// <returns>An <see cref="HttpResponse"/> encapsulating the file transfer or directory listing.</returns> public HttpResponse Handle(HttpRequest request) { if (request.Url.Path == "/$/directory-listing/xsl") return HttpResponse.Create(new MemoryStream(DirectoryListingXsl), "application/xml; charset=utf-8"); if (request.Url.Path.StartsWith("/$/directory-listing/icons/" /* watch out for the hardcoded length below */)) return HttpResponse.Create(new MemoryStream(GetDirectoryListingIcon(request.Url.Path.Substring(27))), "image/png"); if (request.Url.Path.StartsWith("/$/")) throw new HttpNotFoundException(); var dirStyle = (Options ?? DefaultOptions).DirectoryListingStyle; string p = BaseDirectory.EndsWith(Path.DirectorySeparatorChar.ToString()) ? BaseDirectory.Remove(BaseDirectory.Length - 1) : BaseDirectory; string[] urlPieces = request.Url.Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); string soFar = ""; string soFarUrl = ""; for (int i = 0; i < urlPieces.Length; i++) { string piece = urlPieces[i].UrlUnescape(); if (piece == "..") throw new HttpException(HttpStatusCode._403_Forbidden); var candidateFiles = new List<string> { piece }; if (piece.EndsWith(".htm")) candidateFiles.Add(piece + "l"); else if (piece.EndsWith(".html")) candidateFiles.Add(piece.Substring(0, piece.Length - 1)); else if (piece.EndsWith(".jpg")) candidateFiles.Add(piece.Substring(0, piece.Length - 1) + "eg"); else if (piece.EndsWith(".jpeg")) candidateFiles.Add(piece.Substring(0, piece.Length - 2) + "g"); foreach (var suitablePiece in candidateFiles) { string nextSoFar = soFar + Path.DirectorySeparatorChar + suitablePiece; string curPath = p + nextSoFar; if (!File.Exists(curPath) && !Directory.Exists(curPath) && curPath.Contains('*') && dirStyle != DirectoryListingStyle.Forbidden) curPath = new DirectoryInfo(p + soFar).GetFileSystemInfos(suitablePiece).Select(fs => fs.FullName).FirstOrDefault() ?? curPath; if (File.Exists(curPath)) { soFarUrl += "/" + new DirectoryInfo(p + soFar).GetFiles(suitablePiece)[0].Name.UrlEscape(); if (request.Url.Path != soFarUrl) return HttpResponse.Redirect(request.Url.WithPath(soFarUrl)); var opts = Options ?? DefaultOptions; return HttpResponse.File(curPath, opts.GetMimeType(curPath), opts.MaxAge, request.Headers.IfModifiedSince); } else if (Directory.Exists(curPath)) { soFarUrl += "/" + new DirectoryInfo(p + soFar).GetDirectories(suitablePiece)[0].Name.UrlEscape(); soFar = nextSoFar; goto foundOne; } } // The specified piece is neither a file nor a directory. throw new HttpNotFoundException(request.Url.WithPathOnly(soFarUrl + "/" + piece).ToHref()); foundOne:; } // If this point is reached, it’s a directory if (request.Url.Path != soFarUrl + "/") return HttpResponse.Redirect(request.Url.WithPath(soFarUrl + "/")); switch (dirStyle) { case DirectoryListingStyle.Forbidden: throw new HttpException(HttpStatusCode._401_Unauthorized); case DirectoryListingStyle.XmlPlusXsl: var auth = (Options ?? DefaultOptions).DirectoryListingAuth; if (auth != null) { var response = auth(request); if (response != null) return response; } if (!Directory.Exists(p + soFar)) throw new FileNotFoundException("Directory does not exist.", p + soFar); return HttpResponse.Create(generateDirectoryXml(p + soFar, request.Url, soFarUrl + "/"), "application/xml; charset=utf-8"); default: throw new InvalidOperationException("Invalid directory listing style: " + (int) dirStyle); } }
private HttpResponse createUserHandler(HttpRequest req, string loggedInUserName) { if (req.Method != HttpMethod.Post) return createUserForm(req.Url["returnto"], false, false, null, null, null, req.Url.WithoutQuery("returnto")); var username = req.Post["username"].Value; var newpassword = req.Post["newpassword1"].Value; var newpassword2 = req.Post["newpassword2"].Value; var returnTo = req.Post["returnto"].Value; if (username == null || newpassword == null || newpassword2 == null) // if returnTo is null, this removes the query parameter return HttpResponse.Redirect(req.Url.WithQuery("returnto", returnTo)); string passwordHash; bool canCreateUsers; if (!getUser(ref loggedInUserName, out passwordHash, out canCreateUsers) || !canCreateUsers) throw new HttpException(HttpStatusCode._401_Unauthorized); if (newpassword2 != newpassword) // Passwords don’t match. return createUserForm(returnTo, false, true, username, newpassword, newpassword2, req.Url.WithoutQuery("returnto")); if (!createUser(username, createHash(newpassword), false)) // The user already exists. return createUserForm(returnTo, true, false, username, newpassword, newpassword2, req.Url.WithoutQuery("returnto")); // Success. return HttpResponse.Redirect(returnTo ?? _defaultReturnTo(req.Url)); }
private HttpResponse changePasswordHandler(HttpRequest req, string loggedInUser) { if (loggedInUser == null) return HttpResponse.Redirect(req.Url.WithPathParent().WithPathOnly("/login").WithQuery("returnto", req.Url.ToHref())); if (req.Method != HttpMethod.Post) return changePasswordForm(loggedInUser, req.Url["returnto"], false, false, null, null, null, req.Url.WithoutQuery("returnto")); var oldpassword = req.Post["password"].Value; var newpassword = req.Post["newpassword1"].Value; var newpassword2 = req.Post["newpassword2"].Value; var returnTo = req.Post["returnto"].Value; if (loggedInUser == null || oldpassword == null || newpassword == null || newpassword2 == null) return HttpResponse.Redirect(returnTo == null ? req.Url : req.Url.WithQuery("returnto", returnTo)); if (newpassword2 != newpassword) return changePasswordForm(loggedInUser, returnTo, false, true, oldpassword, newpassword, newpassword2, req.Url.WithoutQuery("returnto")); if (!changePassword(loggedInUser, createHash(newpassword), h => verifyHash(oldpassword, h))) return changePasswordForm(loggedInUser, req.Url["returnto"], true, false, oldpassword, newpassword, newpassword2, req.Url.WithoutQuery("returnto")); return HttpResponse.Redirect(returnTo ?? _defaultReturnTo(req.Url)); }
private HttpResponse loginHandler(HttpRequest req, Action<string> setUsername) { if (req.Method != HttpMethod.Post) return loginForm(req.Url["returnto"], false, null, null, req.Url.WithoutQuery("returnto")); var username = req.Post["username"].Value; var password = req.Post["password"].Value; var returnTo = req.Post["returnto"].Value; if (username == null || password == null) return HttpResponse.Redirect(req.Url.WithQuery("returnto", returnTo)); string passwordHash; bool canCreateUsers; if (getUser(ref username, out passwordHash, out canCreateUsers) && verifyHash(password, passwordHash)) { // Login successful! setUsername(username); return HttpResponse.Redirect(returnTo ?? _defaultReturnTo(req.Url)); } // Login failed. return loginForm(returnTo, true, username, password, req.Url); }
private HttpResponse logoutHandler(HttpRequest req, Action<string> setUsername) { setUsername(null); return HttpResponse.Redirect(req.Url.WithParent("login")); }
/// <summary> /// Retrieves the session ID from the session cookie (specified by the cookie name) from the given <see /// cref="HttpRequest"/> and calls the <paramref name="handler"/> function. A session cookie is added to the HTTP /// response returned by the handler only if either <see cref="DeleteSession"/> or <see cref="NewSession"/> have been /// called (possibly by the handler). Before the possibly augmented HTTP response is returned, the status of /// DeleteSession and NewSession is reset.</summary> /// <param name="req"> /// The current HTTP request.</param> /// <param name="handler"> /// The inner request handler to execute.</param> /// <returns> /// An HTTP response, possibly augmented with a session cookie.</returns> public HttpResponse Handle(HttpRequest req, Func<HttpResponse> handler) { if (req == null) throw new ArgumentNullException("req"); if (handler == null) throw new ArgumentNullException("handler"); if (req.Headers != null && req.Headers.Cookie != null && req.Headers.Cookie.ContainsKey(CookieName)) { var cookie = req.Headers.Cookie[CookieName]; SessionID = cookie.Value; CookiePath = cookie.Path; CookieExpires = cookie.Expires; } else { SessionID = null; } var response = handler(); if (response != null && ((_deleteSession && SessionID != null) || _newSession)) { if (response.Headers.SetCookie == null) response.Headers.SetCookie = new List<Cookie>(); response.Headers.SetCookie.Add(new Cookie { Name = CookieName, Value = _deleteSession ? "-" : _newSessionID, Path = _deleteSession ? CookiePath : _newCookiePath, Expires = _deleteSession ? new DateTime(1970, 1, 1) : _newCookieExpires, HttpOnly = true, }); } _deleteSession = false; _newSession = false; return response; }