/** * Respond to a range request, either with the requested bytes, or with a {@code 416 REQUESTED * RANGE NOT SATISFIABLE} response if the requested byte range falls outside the length of the * attachment. If the range request header is syntactically invalid, nothing is written, and * instead {@code false} is returned, letting the action handler ignore the Range header and treat * this as a normal (full) download request. * * @param attachment the attachment to get content from * @param request the current client request * @param response the response to write to. * @param context the current request context * @return {@code true} if the partial content request was syntactically valid and a response was * sent, {@code false} otherwise * @throws XWikiException if the attachment content cannot be retrieved * @throws IOException if the response cannot be written */ private static boolean sendPartialContent( final XWikiAttachment attachment, final XWikiRequest request, final XWikiResponse response, final XWikiContext context) throws XWikiException, IOException { String range = request.getHeader(RANGE_HEADER_NAME); Matcher m = RANGE_HEADER_PATTERN.matcher(range); if (m.matches()) { String startStr = m.group(1); String endStr = m.group(2); Long start = NumberUtils.createLong(startStr); Long end = NumberUtils.createLong(endStr); if (start == null && end != null && end > 0) { // Tail request, output the last <end> bytes start = Math.max(attachment.getContentSize(context) - end, 0L); end = attachment.getContentSize(context) - 1L; } if (!isValidRange(start, end)) { return false; } if (end == null) { end = attachment.getContentSize(context) - 1L; } end = Math.min(end, attachment.getContentSize(context) - 1L); writeByteRange(attachment, start, end, request, response, context); return true; } return false; }
@Override public String render(XWikiContext context) throws XWikiException { XWikiRequest request = context.getRequest(); XWikiResponse response = context.getResponse(); XWikiDocument doc = context.getDoc(); String path = request.getRequestURI(); String filename = Util.decodeURI(getFileName(path, ACTION_NAME), context); XWikiAttachment attachment = null; final String idStr = request.getParameter("id"); if (StringUtils.isNumeric(idStr)) { int id = Integer.parseInt(idStr); if (doc.getAttachmentList().size() > id) { attachment = doc.getAttachmentList().get(id); } } else { attachment = doc.getAttachment(filename); } if (attachment == null) { Object[] args = {filename}; throw new XWikiException( XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND, "Attachment {0} not found", null, args); } XWikiPluginManager plugins = context.getWiki().getPluginManager(); attachment = plugins.downloadAttachment(attachment, context); // Try to load the attachment content just to make sure that the attachment really exists // This will throw an exception if the attachment content isn't available try { attachment.getContentSize(context); } catch (XWikiException e) { Object[] args = {filename}; throw new XWikiException( XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND, "Attachment content {0} not found", null, args); } long lastModifiedOnClient = request.getDateHeader("If-Modified-Since"); long lastModifiedOnServer = attachment.getDate().getTime(); if (lastModifiedOnClient != -1 && lastModifiedOnClient >= lastModifiedOnServer) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return null; } // Sending the content of the attachment if (request.getHeader(RANGE_HEADER_NAME) != null) { try { if (sendPartialContent(attachment, request, response, context)) { return null; } } catch (IOException ex) { // Broken response... } } sendContent(attachment, request, response, filename, context); return null; }