-
Notifications
You must be signed in to change notification settings - Fork 0
[View] 공통 요소 정적 뷰에 적용, 에러 팝업 개발 #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
a4d038f
4ab02e3
579ce4d
325103b
f3d71c9
c7b88d9
b04c211
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package web.handler; | ||
|
|
||
| import config.VariableConfig; | ||
| import exception.ErrorException; | ||
| import http.HttpMethod; | ||
| import http.HttpStatus; | ||
| import http.request.HttpRequest; | ||
| import web.response.DynamicViewResponse; | ||
| import web.response.HandlerResponse; | ||
|
|
||
| import java.io.File; | ||
| import java.util.List; | ||
|
|
||
| public class DefaultViewHandler implements DefaultHandler{ | ||
| private final List<String> roots = VariableConfig.DYNAMIC_RESOURCE_ROOTS; | ||
| private final HttpMethod method = HttpMethod.GET; | ||
|
|
||
| @Override | ||
| public String getPath() { | ||
| throw new ErrorException("DynamicViewHandler::getPath should not be called"); | ||
| } | ||
|
|
||
| @Override | ||
| public HttpMethod getMethod() { | ||
| return this.method; | ||
| } | ||
| @Override | ||
| public boolean checkEndpoint(HttpMethod method, String path) { | ||
| if(!method.equals(this.method)) return false; | ||
| return roots.stream().anyMatch(root ->{ | ||
| File requestedFile = new File(root + path); | ||
| String indexFilePath = path + (path.endsWith("/") ? "index.html" : "/index.html"); | ||
| File indexFile = new File(root + indexFilePath); | ||
| return (requestedFile.exists() && requestedFile.isFile()) || (indexFile.exists() && indexFile.isFile()); | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| public HandlerResponse handle(HttpRequest request) { | ||
| String path = request.getPath() + (request.getPath().endsWith("/") ? "index.html" : "/index.html"); | ||
| return DynamicViewResponse.of(HttpStatus.OK, path); | ||
| } | ||
|
Comment on lines
+39
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인덱스 파일 로직 중복: 제안: 경로 정규화 로직을 한 곳(예: static 메서드)에 집중시키세요. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| <style> | ||
| .error-popup-wrapper { | ||
| position: fixed; | ||
| top: 16px; | ||
| right: 50px; | ||
| z-index: 1000; | ||
| } | ||
| .error-popup { | ||
| display: none; | ||
| background: #ffffff; | ||
| border-radius: 8px; | ||
| padding: 14px 18px; | ||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); | ||
| min-width: 260px; | ||
| } | ||
| .error-popup__title { | ||
| font-size: 14px; | ||
| font-weight: 600; | ||
| margin-bottom: 6px; | ||
| color: #d32f2f; | ||
| } | ||
| .error-popup__message { | ||
| font-size: 13px; | ||
| margin-bottom: 10px; | ||
| } | ||
| .error-popup__actions { | ||
| display: flex; | ||
| justify-content: flex-end; | ||
| } | ||
| .error-popup__ok-btn { | ||
| cursor: pointer; | ||
| padding: 4px 12px; | ||
| font-size: 13px; | ||
| border-radius: 4px; | ||
| border: none; | ||
| } | ||
| </style> | ||
|
|
||
| <div class="error-popup-wrapper"> | ||
| <div id="error-popup" class="error-popup"> | ||
| <div class="error-popup__title">알림</div> | ||
| <div id="error-popup-message" class="error-popup__message"></div> | ||
| <div class="error-popup__actions"> | ||
| <button id="error-popup-ok" class="error-popup__ok-btn" type="button"> | ||
| OK | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <script> | ||
| (function () { | ||
| const popup = document.getElementById("error-popup"); | ||
| const msgEl = document.getElementById("error-popup-message"); | ||
| const okBtn = document.getElementById("error-popup-ok"); | ||
|
|
||
| window.showErrorPopup = function (message, title) { | ||
| const titleEl = popup.querySelector(".error-popup__title"); | ||
| msgEl.textContent = message || "오류가 발생했습니다."; | ||
| if (title) titleEl.textContent = title; | ||
| popup.style.display = "block"; | ||
| }; | ||
|
|
||
| window.hideErrorPopup = function () { | ||
| popup.style.display = "none"; | ||
| }; | ||
|
|
||
| okBtn.addEventListener("click", () => window.hideErrorPopup()); | ||
|
|
||
| // ---- 공통 submit 가로채기 (B안: data-ajax 있는 폼만) ---- | ||
| document.addEventListener("submit", async (e) => { | ||
| const form = e.target; | ||
| if (!(form instanceof HTMLFormElement)) return; | ||
|
|
||
| // 안전하게: data-ajax="true"인 폼만 가로챔 | ||
| if (form.dataset.ajax !== "true") return; | ||
|
|
||
| e.preventDefault(); | ||
|
|
||
| // multipart/form-data는 여기서 처리 안 함(파일 업로드 등) | ||
| const enctype = (form.enctype || "").toLowerCase(); | ||
| if (enctype.includes("multipart/form-data")) { | ||
| window.showErrorPopup("파일 업로드 폼은 지원하지 않습니다.", "오류"); | ||
| return; | ||
|
Comment on lines
+77
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 폼 필터링 문제: 제안: AJAX 처리 대상 폼을 명시적으로 관리하거나, 특정 클래스(예:
Comment on lines
+79
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 멀티파트 폼 제한 사항: 제안:
이 부분은 mypage의 프로필 이미지 업로드 기능과 연관될 수 있으므로 주의가 필요합니다. |
||
| } | ||
|
|
||
| const body = new URLSearchParams(new FormData(form)); | ||
|
|
||
| try { | ||
| const res = await fetch(form.action, { | ||
| method: (form.method || "POST").toUpperCase(), | ||
| headers: { | ||
| "Accept": "application/json", | ||
| "Content-Type": "application/x-www-form-urlencoded", | ||
| }, | ||
| body, | ||
| credentials: "same-origin", | ||
| }); | ||
|
|
||
| if (res.ok) { | ||
| // 성공 처리: 서버가 리다이렉트를 주는 구조면 여기서 location 설정 | ||
| // 우선 가장 단순하게는 reload 혹은 홈 이동 | ||
| window.location.href = "/"; | ||
| return; | ||
| } | ||
|
Comment on lines
98
to
106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 폼 제출 후 처리 로직 불명확: 제안: 폼에 |
||
|
|
||
| let err = null; | ||
| try { err = await res.json(); } catch {} | ||
|
Comment on lines
71
to
109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. XSS 취약점: 제안:
예시: const formUrl = new URL(form.action, window.location.origin);
if (formUrl.origin !== window.location.origin) {
window.showErrorPopup("유효하지 않은 폼 대상입니다.", "오류");
return;
} |
||
|
|
||
| const message = | ||
| (err && (err.message || err.errorMessage)) || | ||
| "요청 처리에 실패했습니다."; | ||
|
|
||
| window.showErrorPopup(message, "요청 실패"); | ||
| } catch (networkError) { | ||
| window.showErrorPopup("서버와 통신 중 문제가 발생했습니다.", "오류"); | ||
| } | ||
| }, true); // capture로 잡으면 더 안정적으로 submit을 가로챌 수 있음 | ||
| })(); | ||
| </script> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,10 @@ | |
| <ul class="header__menu"> | ||
| {{#if1 isLoginUser}} | ||
| <li class="header__menu__item"> | ||
| <span class="header__menu__nickname">닉네임: {{userNickname}}</span> | ||
| <a class="header__menu__nickname" href="/mypage">닉네임: {{userNickname}}</a> | ||
| </li> | ||
| <li class="header__menu__item"> | ||
| <a class="btn btn_contained btn_size_s" href="/article">글쓰기</a> | ||
| </li> | ||
|
Comment on lines
6
to
11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스타일 일관성 문제: 닉네임을 제안: CSS 파일에서 |
||
| <form action="/user/logout" method="POST"> | ||
| <button type="submit">로그아웃</button> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
디렉토리 트래버설(Path Traversal) 위험:
checkEndpoint()메서드에서 경로 검증이 불충분합니다. 사용자가../../../etc/passwd같은 경로를 요청할 경우,new File(root + path)후.exists()검사만으로는 보호되지 않습니다. (Java의 File API는 심볼릭 링크나 경로 정규화 문제가 있을 수 있음)제안: