| /* | |
| * Copyright 2011 gitblit.com. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package com.gitblit.wicket.pages; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.text.MessageFormat; | |
| import java.util.ArrayList; | |
| import java.util.Calendar; | |
| import java.util.Collections; | |
| import java.util.Date; | |
| import java.util.HashSet; | |
| import java.util.LinkedHashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.ResourceBundle; | |
| import java.util.Set; | |
| import java.util.TimeZone; | |
| import java.util.regex.Pattern; | |
| import javax.servlet.http.HttpServletRequest; | |
| import org.apache.commons.io.IOUtils; | |
| import org.apache.wicket.Application; | |
| import org.apache.wicket.Component; | |
| import org.apache.wicket.Page; | |
| import org.apache.wicket.PageParameters; | |
| import org.apache.wicket.RedirectToUrlException; | |
| import org.apache.wicket.markup.html.CSSPackageResource; | |
| import org.apache.wicket.markup.html.basic.Label; | |
| import org.apache.wicket.markup.html.link.ExternalLink; | |
| import org.apache.wicket.markup.html.panel.FeedbackPanel; | |
| import org.apache.wicket.markup.html.resources.JavascriptResourceReference; | |
| import org.apache.wicket.markup.repeater.RepeatingView; | |
| import org.apache.wicket.protocol.http.RequestUtils; | |
| import org.apache.wicket.protocol.http.WebResponse; | |
| import org.apache.wicket.protocol.http.servlet.ServletWebRequest; | |
| import org.apache.wicket.request.target.basic.RedirectRequestTarget; | |
| import org.apache.wicket.util.time.Duration; | |
| import org.apache.wicket.util.time.Time; | |
| import org.slf4j.Logger; | |
| import org.slf4j.LoggerFactory; | |
| import com.gitblit.Constants; | |
| import com.gitblit.Constants.AccessPermission; | |
| import com.gitblit.Constants.AccessRestrictionType; | |
| import com.gitblit.Constants.AuthorizationControl; | |
| import com.gitblit.Constants.FederationStrategy; | |
| import com.gitblit.Keys; | |
| import com.gitblit.models.ProjectModel; | |
| import com.gitblit.models.TeamModel; | |
| import com.gitblit.models.UserModel; | |
| import com.gitblit.utils.StringUtils; | |
| import com.gitblit.utils.TimeUtils; | |
| import com.gitblit.wicket.CacheControl; | |
| import com.gitblit.wicket.GitBlitWebApp; | |
| import com.gitblit.wicket.GitBlitWebSession; | |
| import com.gitblit.wicket.WicketUtils; | |
| public abstract class BasePage extends SessionPage { | |
| private transient Logger logger; | |
| private transient TimeUtils timeUtils; | |
| public BasePage() { | |
| super(); | |
| add(new RepeatingView("bottomScripts").setRenderBodyOnly(true)); | |
| customizeHeader(); | |
| } | |
| public BasePage(PageParameters params) { | |
| super(params); | |
| add(new RepeatingView("bottomScripts").setRenderBodyOnly(true)); | |
| customizeHeader(); | |
| } | |
| protected Logger logger() { | |
| if (logger == null) { | |
| logger = LoggerFactory.getLogger(getClass()); | |
| } | |
| return logger; | |
| } | |
| private void customizeHeader() { | |
| if (app().settings().getBoolean(Keys.web.useResponsiveLayout, true)) { | |
| add(CSSPackageResource.getHeaderContribution("bootstrap/css/bootstrap-responsive.css")); | |
| } | |
| if (app().settings().getBoolean(Keys.web.hideHeader, false)) { | |
| add(CSSPackageResource.getHeaderContribution("hideheader.css")); | |
| } | |
| } | |
| protected String getContextUrl() { | |
| return getRequest().getRelativePathPrefixToContextRoot(); | |
| } | |
| protected String getCanonicalUrl() { | |
| return getCanonicalUrl(getClass(), getPageParameters()); | |
| } | |
| protected String getCanonicalUrl(Class<? extends BasePage> clazz, PageParameters params) { | |
| String relativeUrl = urlFor(clazz, params).toString(); | |
| String canonicalUrl = RequestUtils.toAbsolutePath(relativeUrl); | |
| return canonicalUrl; | |
| } | |
| protected void redirectTo(Class<? extends BasePage> pageClass) { | |
| redirectTo(pageClass, null); | |
| } | |
| protected void redirectTo(Class<? extends BasePage> pageClass, PageParameters parameters) { | |
| String absoluteUrl = getCanonicalUrl(pageClass, parameters); | |
| getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl)); | |
| } | |
| protected String getLanguageCode() { | |
| return GitBlitWebSession.get().getLocale().getLanguage(); | |
| } | |
| protected String getCountryCode() { | |
| return GitBlitWebSession.get().getLocale().getCountry().toLowerCase(); | |
| } | |
| protected TimeUtils getTimeUtils() { | |
| if (timeUtils == null) { | |
| ResourceBundle bundle; | |
| try { | |
| bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", GitBlitWebSession.get().getLocale()); | |
| } catch (Throwable t) { | |
| bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp"); | |
| } | |
| timeUtils = new TimeUtils(bundle, getTimeZone()); | |
| } | |
| return timeUtils; | |
| } | |
| @Override | |
| protected void onBeforeRender() { | |
| if (app().isDebugMode()) { | |
| // strip Wicket tags in debug mode for jQuery DOM traversal | |
| Application.get().getMarkupSettings().setStripWicketTags(true); | |
| } | |
| super.onBeforeRender(); | |
| } | |
| @Override | |
| protected void onAfterRender() { | |
| if (app().isDebugMode()) { | |
| // restore Wicket debug tags | |
| Application.get().getMarkupSettings().setStripWicketTags(false); | |
| } | |
| super.onAfterRender(); | |
| } | |
| @Override | |
| protected void setHeaders(WebResponse response) { | |
| // set canonical link as http header for SEO (issue-304) | |
| // https://support.google.com/webmasters/answer/139394?hl=en | |
| response.setHeader("Link", MessageFormat.format("<{0}>; rel=\"canonical\"", getCanonicalUrl())); | |
| int expires = app().settings().getInteger(Keys.web.pageCacheExpires, 0); | |
| if (expires > 0) { | |
| // pages are personalized for the authenticated user so they must be | |
| // marked private to prohibit proxy servers from caching them | |
| response.setHeader("Cache-Control", "private, must-revalidate"); | |
| setLastModified(); | |
| } else { | |
| // use default Wicket caching behavior | |
| super.setHeaders(response); | |
| } | |
| // XRF vulnerability. issue-500 / ticket-166 | |
| response.setHeader("X-Frame-Options", "SAMEORIGIN"); | |
| } | |
| /** | |
| * Sets the last-modified header date, if appropriate, for this page. The | |
| * date used is determined by the CacheControl annotation. | |
| * | |
| */ | |
| protected void setLastModified() { | |
| if (getClass().isAnnotationPresent(CacheControl.class)) { | |
| CacheControl cacheControl = getClass().getAnnotation(CacheControl.class); | |
| switch (cacheControl.value()) { | |
| case ACTIVITY: | |
| setLastModified(app().getLastActivityDate()); | |
| break; | |
| case BOOT: | |
| setLastModified(app().getBootDate()); | |
| break; | |
| case NONE: | |
| break; | |
| default: | |
| logger().warn(getClass().getSimpleName() + ": unhandled LastModified type " + cacheControl.value()); | |
| break; | |
| } | |
| } | |
| } | |
| /** | |
| * Sets the last-modified header field and the expires field. | |
| * | |
| * @param when | |
| */ | |
| protected final void setLastModified(Date when) { | |
| if (when == null) { | |
| return; | |
| } | |
| if (when.before(app().getBootDate())) { | |
| // last-modified can not be before the Gitblit boot date | |
| // this helps ensure that pages are properly refreshed after a | |
| // server config change | |
| when = app().getBootDate(); | |
| } | |
| int expires = app().settings().getInteger(Keys.web.pageCacheExpires, 0); | |
| WebResponse response = (WebResponse) getResponse(); | |
| response.setLastModifiedTime(Time.valueOf(when)); | |
| response.setDateHeader("Expires", System.currentTimeMillis() + Duration.minutes(expires).getMilliseconds()); | |
| } | |
| protected String getPageTitle(String repositoryName) { | |
| String siteName = app().settings().getString(Keys.web.siteName, Constants.NAME); | |
| if (StringUtils.isEmpty(siteName)) { | |
| siteName = Constants.NAME; | |
| } | |
| if (repositoryName != null && repositoryName.trim().length() > 0) { | |
| return repositoryName + " - " + siteName; | |
| } else { | |
| return siteName; | |
| } | |
| } | |
| protected void setupPage(String repositoryName, String pageName) { | |
| add(new Label("title", getPageTitle(repositoryName))); | |
| String rootLinkUrl = app().settings().getString(Keys.web.rootLink, urlFor(GitBlitWebApp.get().getHomePage(), null).toString()); | |
| ExternalLink rootLink = new ExternalLink("rootLink", rootLinkUrl); | |
| WicketUtils.setHtmlTooltip(rootLink, app().settings().getString(Keys.web.siteName, Constants.NAME)); | |
| add(rootLink); | |
| // Feedback panel for info, warning, and non-fatal error messages | |
| add(new FeedbackPanel("feedback")); | |
| add(new Label("gbVersion", "v" + Constants.getVersion())); | |
| if (app().settings().getBoolean(Keys.web.aggressiveHeapManagement, false)) { | |
| System.gc(); | |
| } | |
| } | |
| protected Map<AccessRestrictionType, String> getAccessRestrictions() { | |
| Map<AccessRestrictionType, String> map = new LinkedHashMap<AccessRestrictionType, String>(); | |
| for (AccessRestrictionType type : AccessRestrictionType.values()) { | |
| switch (type) { | |
| case NONE: | |
| map.put(type, getString("gb.notRestricted")); | |
| break; | |
| case PUSH: | |
| map.put(type, getString("gb.pushRestricted")); | |
| break; | |
| case CLONE: | |
| map.put(type, getString("gb.cloneRestricted")); | |
| break; | |
| case VIEW: | |
| map.put(type, getString("gb.viewRestricted")); | |
| break; | |
| } | |
| } | |
| return map; | |
| } | |
| protected Map<AccessPermission, String> getAccessPermissions() { | |
| Map<AccessPermission, String> map = new LinkedHashMap<AccessPermission, String>(); | |
| for (AccessPermission type : AccessPermission.values()) { | |
| switch (type) { | |
| case NONE: | |
| map.put(type, MessageFormat.format(getString("gb.noPermission"), type.code)); | |
| break; | |
| case EXCLUDE: | |
| map.put(type, MessageFormat.format(getString("gb.excludePermission"), type.code)); | |
| break; | |
| case VIEW: | |
| map.put(type, MessageFormat.format(getString("gb.viewPermission"), type.code)); | |
| break; | |
| case CLONE: | |
| map.put(type, MessageFormat.format(getString("gb.clonePermission"), type.code)); | |
| break; | |
| case PUSH: | |
| map.put(type, MessageFormat.format(getString("gb.pushPermission"), type.code)); | |
| break; | |
| case CREATE: | |
| map.put(type, MessageFormat.format(getString("gb.createPermission"), type.code)); | |
| break; | |
| case DELETE: | |
| map.put(type, MessageFormat.format(getString("gb.deletePermission"), type.code)); | |
| break; | |
| case REWIND: | |
| map.put(type, MessageFormat.format(getString("gb.rewindPermission"), type.code)); | |
| break; | |
| } | |
| } | |
| return map; | |
| } | |
| protected Map<FederationStrategy, String> getFederationTypes() { | |
| Map<FederationStrategy, String> map = new LinkedHashMap<FederationStrategy, String>(); | |
| for (FederationStrategy type : FederationStrategy.values()) { | |
| switch (type) { | |
| case EXCLUDE: | |
| map.put(type, getString("gb.excludeFromFederation")); | |
| break; | |
| case FEDERATE_THIS: | |
| map.put(type, getString("gb.federateThis")); | |
| break; | |
| case FEDERATE_ORIGIN: | |
| map.put(type, getString("gb.federateOrigin")); | |
| break; | |
| } | |
| } | |
| return map; | |
| } | |
| protected Map<AuthorizationControl, String> getAuthorizationControls() { | |
| Map<AuthorizationControl, String> map = new LinkedHashMap<AuthorizationControl, String>(); | |
| for (AuthorizationControl type : AuthorizationControl.values()) { | |
| switch (type) { | |
| case AUTHENTICATED: | |
| map.put(type, getString("gb.allowAuthenticatedDescription")); | |
| break; | |
| case NAMED: | |
| map.put(type, getString("gb.allowNamedDescription")); | |
| break; | |
| } | |
| } | |
| return map; | |
| } | |
| protected TimeZone getTimeZone() { | |
| return app().settings().getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get() | |
| .getTimezone() : app().getTimezone(); | |
| } | |
| protected String getServerName() { | |
| ServletWebRequest servletWebRequest = (ServletWebRequest) getRequest(); | |
| HttpServletRequest req = servletWebRequest.getHttpServletRequest(); | |
| return req.getServerName(); | |
| } | |
| protected List<ProjectModel> getProjectModels() { | |
| final UserModel user = GitBlitWebSession.get().getUser(); | |
| List<ProjectModel> projects = app().projects().getProjectModels(user, true); | |
| return projects; | |
| } | |
| protected List<ProjectModel> getProjects(PageParameters params) { | |
| if (params == null) { | |
| return getProjectModels(); | |
| } | |
| boolean hasParameter = false; | |
| String regex = WicketUtils.getRegEx(params); | |
| String team = WicketUtils.getTeam(params); | |
| int daysBack = params.getInt("db", 0); | |
| int maxDaysBack = app().settings().getInteger(Keys.web.activityDurationMaximum, 30); | |
| List<ProjectModel> availableModels = getProjectModels(); | |
| Set<ProjectModel> models = new HashSet<ProjectModel>(); | |
| if (!StringUtils.isEmpty(regex)) { | |
| // filter the projects by the regex | |
| hasParameter = true; | |
| Pattern pattern = Pattern.compile(regex); | |
| for (ProjectModel model : availableModels) { | |
| if (pattern.matcher(model.name).find()) { | |
| models.add(model); | |
| } | |
| } | |
| } | |
| if (!StringUtils.isEmpty(team)) { | |
| // filter the projects by the specified teams | |
| hasParameter = true; | |
| List<String> teams = StringUtils.getStringsFromValue(team, ","); | |
| // need TeamModels first | |
| List<TeamModel> teamModels = new ArrayList<TeamModel>(); | |
| for (String name : teams) { | |
| TeamModel teamModel = app().users().getTeamModel(name); | |
| if (teamModel != null) { | |
| teamModels.add(teamModel); | |
| } | |
| } | |
| // brute-force our way through finding the matching models | |
| for (ProjectModel projectModel : availableModels) { | |
| for (String repositoryName : projectModel.repositories) { | |
| for (TeamModel teamModel : teamModels) { | |
| if (teamModel.hasRepositoryPermission(repositoryName)) { | |
| models.add(projectModel); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (!hasParameter) { | |
| models.addAll(availableModels); | |
| } | |
| // time-filter the list | |
| if (daysBack > 0) { | |
| if (maxDaysBack > 0 && daysBack > maxDaysBack) { | |
| daysBack = maxDaysBack; | |
| } | |
| Calendar cal = Calendar.getInstance(); | |
| cal.set(Calendar.HOUR_OF_DAY, 0); | |
| cal.set(Calendar.MINUTE, 0); | |
| cal.set(Calendar.SECOND, 0); | |
| cal.set(Calendar.MILLISECOND, 0); | |
| cal.add(Calendar.DATE, -1 * daysBack); | |
| Date threshold = cal.getTime(); | |
| Set<ProjectModel> timeFiltered = new HashSet<ProjectModel>(); | |
| for (ProjectModel model : models) { | |
| if (model.lastChange.after(threshold)) { | |
| timeFiltered.add(model); | |
| } | |
| } | |
| models = timeFiltered; | |
| } | |
| List<ProjectModel> list = new ArrayList<ProjectModel>(models); | |
| Collections.sort(list); | |
| return list; | |
| } | |
| public void warn(String message, Throwable t) { | |
| logger().warn(message, t); | |
| } | |
| public void error(String message, boolean redirect) { | |
| error(message, null, redirect ? getApplication().getHomePage() : null); | |
| } | |
| public void error(String message, Throwable t, boolean redirect) { | |
| error(message, t, getApplication().getHomePage()); | |
| } | |
| public void error(String message, Throwable t, Class<? extends Page> toPage) { | |
| error(message, t, toPage, null); | |
| } | |
| public void error(String message, Throwable t, Class<? extends Page> toPage, PageParameters params) { | |
| if (t == null) { | |
| logger().error(message + " for " + GitBlitWebSession.get().getUsername()); | |
| } else { | |
| logger().error(message + " for " + GitBlitWebSession.get().getUsername(), t); | |
| } | |
| if (toPage != null) { | |
| GitBlitWebSession.get().cacheErrorMessage(message); | |
| String relativeUrl = urlFor(toPage, params).toString(); | |
| String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl); | |
| throw new RedirectToUrlException(absoluteUrl); | |
| } else { | |
| super.error(message); | |
| } | |
| } | |
| public void authenticationError(String message) { | |
| logger().error(getRequest().getURL() + " for " + GitBlitWebSession.get().getUsername()); | |
| if (!GitBlitWebSession.get().isLoggedIn()) { | |
| // cache the request if we have not authenticated. | |
| // the request will continue after authentication. | |
| GitBlitWebSession.get().cacheRequest(getClass()); | |
| } | |
| error(message, true); | |
| } | |
| protected String readResource(String resource) { | |
| StringBuilder sb = new StringBuilder(); | |
| InputStream is = null; | |
| try { | |
| is = getClass().getResourceAsStream(resource); | |
| List<String> lines = IOUtils.readLines(is); | |
| for (String line : lines) { | |
| sb.append(line).append('\n'); | |
| } | |
| } catch (IOException e) { | |
| } finally { | |
| if (is != null) { | |
| try { | |
| is.close(); | |
| } catch (IOException e) { | |
| } | |
| } | |
| } | |
| return sb.toString(); | |
| } | |
| /** | |
| * Adds a HTML script element loading the javascript designated by the given path. | |
| * | |
| * @param scriptPath | |
| * page-relative path to the Javascript resource; normally starts with "scripts/" | |
| */ | |
| protected void addBottomScript(String scriptPath) { | |
| Component bottomScriptContainer = get("bottomScripts"); | |
| if (bottomScriptContainer instanceof RepeatingView) { | |
| // Always true. | |
| RepeatingView bottomScripts = (RepeatingView) bottomScriptContainer; | |
| Label script = new Label(bottomScripts.newChildId(), "<script type='text/javascript' src='" | |
| + urlFor(new JavascriptResourceReference(this.getClass(), scriptPath)) + "'></script>\n"); | |
| bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true)); | |
| } | |
| } | |
| /** | |
| * Adds a HTML script element containing the given code. | |
| * | |
| * @param code | |
| * inline script code | |
| */ | |
| protected void addBottomScriptInline(String code) { | |
| Component bottomScriptContainer = get("bottomScripts"); | |
| if (bottomScriptContainer instanceof RepeatingView) { | |
| // Always true. | |
| RepeatingView bottomScripts = (RepeatingView) bottomScriptContainer; | |
| Label script = new Label(bottomScripts.newChildId(), | |
| "<script type='text/javascript'>/*<![CDATA[*/\n" + code + "\n//]]>\n</script>\n"); | |
| bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true)); | |
| } | |
| } | |
| } |