Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion api/src/org/labkey/api/security/GroupManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ private static void appendDotAttribute(StringBuilder sb, boolean prependComma, S
if (prependComma)
sb.append(", ");

sb.append(name).append("=\"").append(value).append("\"");
// Escape backslashes first, then quotes, to produce a valid DOT quoted string
String escaped = value.replace("\\", "\\\\").replace("\"", "\\\"");
sb.append(name).append("=\"").append(escaped).append("\"");
}

public static void exportGroupMembers(Group group, List<Group> memberGroups, List<User> memberUsers, GroupType xmlGroupType)
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/util/MemTrackerListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public interface MemTrackerListener
{
/**
* Called before GC and tallying of held objects. Implementors should purge held objects and (optionally) add
* objects to the passed in set that should be ignored .
* objects to the passed in set that should be ignored.
*/
void beforeReport(Set<Object> set);
}
14 changes: 0 additions & 14 deletions api/src/org/labkey/api/view/template/errorView.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,6 @@

<div id="<%=h(appId)%>"></div>

<%
StringBuilder stackTrace = new StringBuilder();
if (null != model.getException())
{
stackTrace.append(model.getException().toString());
for (StackTraceElement stackTraceElement : model.getException().getStackTrace())
{
stackTrace.append("\n");
stackTrace.append(stackTraceElement.toString());
}
}
%>

<script type="text/javascript" nonce="<%=getScriptNonce()%>">
/*
This error page may be invoked without the themes having been loaded for this container.
Expand All @@ -52,7 +39,6 @@
errorDetails : {
message: <%=q(model.getHeading())%>,
errorType: <%=q(model.getErrorType())%>,
stackTrace: <%=q(stackTrace.toString())%>,
errorCode: <%=q(model.getErrorCode())%>,
advice: <%=q(model.getAdvice())%>
}
Expand Down
4 changes: 0 additions & 4 deletions core/src/client/ErrorHandler/ErrorHandler.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,15 @@ describe('ErrorHandlerImpl', () => {
});

test('Execution exception', async () => {
const expectedStackTrace = 'java.lang.NullPointerException: null';

const errorDetails: ErrorDetails = {
errorType: ErrorType.execution,
errorCode: '456AAA',
message: 'This is a execution exception',
stackTrace: expectedStackTrace,
};
renderWithAppContext(<ErrorHandlerImpl context={{ errorDetails }} />);
expect(document.querySelector('.labkey-error-subheading').innerHTML.includes(errorDetails.message)).toBeTruthy();
expect(document.querySelectorAll('.error-details-container')).toHaveLength(0);

await userEvent.click(document.querySelectorAll('.error-page-button')[2]);
expect(document.querySelector('pre').innerHTML.startsWith(expectedStackTrace)).toBeTruthy();
});
});
4 changes: 2 additions & 2 deletions core/src/client/ErrorHandler/ErrorHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react';
import { ActionURL } from '@labkey/api';

import { getErrorHeading, getImage, getInstruction, getSubHeading, getViewDetails } from './ErrorType';
import { getErrorHeading, getImage, getInstruction, getShowDetailsBtn, getSubHeading, getViewDetails } from './ErrorType';
import { ErrorDetails } from './model';
import { withServerContext } from '@labkey/components';

Expand Down Expand Up @@ -61,7 +61,7 @@ export class ErrorHandlerImpl extends PureComponent<ErrorHandlerProps, ErrorHand
<button className="btn btn-default error-page-button" onClick={this.onHomeClick}>
Home
</button>
{!errorDetails.hideViewDetails &&
{!errorDetails.hideViewDetails && getShowDetailsBtn(errorDetails) &&
<button className="btn btn-default error-page-button" onClick={this.onViewDetailsClick}>
{viewDetailsBtnText}
</button>
Expand Down
16 changes: 12 additions & 4 deletions core/src/client/ErrorHandler/ErrorType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ const CONFIGURATION_DETAILS = (errorDetails: ErrorDetails) => (
</div>

{DETAILS_SUB_INSTRUCTION}
<pre>{errorDetails.stackTrace}</pre>
</>
);

Expand All @@ -231,21 +230,21 @@ const EXECUTION_INSTRUCTION = (errorDetails: ErrorDetails) => (
{' '}
find help resources here{' '}
</a>{' '}
and may find troubleshooting hints by reading the full stack trace in the View Details section
below.
and may find troubleshooting hints by reading the full stack trace in the server logs.
</div>
<div className="labkey-error-instruction">
Your unique reference code is: <b>{errorDetails.errorCode}</b>
</div>
</>
);
const EXECUTION_DETAILS = (errorDetails: ErrorDetails) => <pre>{errorDetails.stackTrace}</pre>;
const EXECUTION_DETAILS = () => null;

type ErrorTypeInfo = {
details: (errorDetails?: ErrorDetails) => ReactNode;
heading: (errorMessage?: string) => ReactNode;
imagePath: string;
instruction: (errorDetails?: ErrorDetails) => ReactNode;
showDetailsBtn: boolean;
subHeading: (errorMessage?: string) => ReactNode;
};

Expand All @@ -257,27 +256,31 @@ const ERROR_TYPE_INFO: { [key in ErrorType]: ErrorTypeInfo } = {
heading: CONFIGURATION_HEADING,
imagePath: 'configuration_error.svg',
instruction: CONFIGURATION_INSTRUCTION,
showDetailsBtn: true,
subHeading: CONFIGURATION_SUBHEADING,
},
execution: {
details: EXECUTION_DETAILS,
heading: ERROR_HEADING,
imagePath: 'code_error.svg',
instruction: EXECUTION_INSTRUCTION,
showDetailsBtn: false,
subHeading: EXECUTION_SUB_HEADING,
},
notFound: {
details: NOTFOUND_DETAILS,
heading: NOTFOUND_HEADING,
imagePath: 'notFound_error.svg',
instruction: NOTFOUND_INSTRUCTION,
showDetailsBtn: true,
subHeading: NOTFOUND_SUBHEADING,
},
permission: {
details: PERMISSION_DETAILS,
heading: ERROR_HEADING,
imagePath: 'permission_error.svg',
instruction: PERMISSION_INSTRUCTION,
showDetailsBtn: true,
subHeading: PERMISSION_SUBHEADING,
},
};
Expand Down Expand Up @@ -310,6 +313,11 @@ export const getInstruction = (errorDetails: ErrorDetails): ReactNode => {
return <div className="labkey-error-instruction">{info.instruction(errorDetails)}</div>;
};

export const getShowDetailsBtn = (errorDetails: ErrorDetails): boolean => {
const info = ERROR_TYPE_INFO[errorDetails.errorType];
return info?.showDetailsBtn ?? true;
};

export const getViewDetails = (errorDetails: ErrorDetails): ReactNode => {
const info = ERROR_TYPE_INFO[errorDetails.errorType];
if (!info) return null;
Expand Down
1 change: 0 additions & 1 deletion core/src/client/ErrorHandler/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export interface ErrorDetails {
errorCode?: string;
errorType: ErrorType;
message?: string;
stackTrace?: string;
hideViewDetails?: boolean;
advice?: string;
}
14 changes: 12 additions & 2 deletions core/src/org/labkey/core/CoreController.java
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ else if (form.getSchemaName() != null && form.getQueryName() != null && form.get
{
throw new NotFoundException("The file '" + file.getName() + "' attached to the object '" + identifiable.getName() + "' cannot be found. It may have been deleted.");
}
throw new NotFoundException("File " + file.getPath() + " does not exist on the server file system. It may have been deleted.");
throw new NotFoundException("File " + file.getName() + " does not exist on the server file system. It may have been deleted.");
}

if (file.isDirectory())
Expand Down Expand Up @@ -654,6 +654,7 @@ private static byte[] compressCSS(String s)
catch (StackOverflowError e)
{
// replaceAll() can blow up
_log.error("StackOverflowError compressing CSS");
}
return Compress.compressGzip(c.trim());
}
Expand Down Expand Up @@ -935,6 +936,11 @@ public void validateForm(SimpleApiJsonForm form, Errors errors)
errors.reject(ERROR_MSG, "The container '" + parentIdentifier + "' is not a valid parent folder.");
return;
}

if (!target.hasPermission(getUser(), AdminPermission.class))
{
throw new UnauthorizedException("You must be an administrator for the target container");
}
}

@Override
Expand Down Expand Up @@ -2390,6 +2396,10 @@ public Object execute(Object o, BindException errors)
}
}

/**
* This action doesn't require any permissions, as the call to WarningService.getWarnings()
* only returns warnings appropriate for the user/guest
*/
@RequiresNoPermission
@AllowedDuringUpgrade
public static class DisplayWarningsAction extends MutatingApiAction<Object>
Expand Down Expand Up @@ -2721,7 +2731,7 @@ public void setToFormat(String toFormat)
}

@SuppressWarnings("unused") // Called from JavaScript: discuss.js, wikiEdit.js
@RequiresNoPermission
@RequiresPermission(ReadPermission.class)
public static class TransformWikiAction extends MutatingApiAction<TransformWikiForm>
{
@Override
Expand Down
Loading
Loading