Java tutorial
/** * Copyright (c) 2009--2014 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package com.redhat.rhn.frontend.action.common; import com.redhat.rhn.common.conf.Config; import com.redhat.rhn.common.conf.ConfigDefaults; import com.redhat.rhn.common.security.SessionSwap; import com.redhat.rhn.common.util.FileUtils; import com.redhat.rhn.common.util.MD5Sum; import com.redhat.rhn.common.util.download.ByteArrayStreamInfo; import com.redhat.rhn.domain.channel.Channel; import com.redhat.rhn.domain.channel.ChannelFactory; import com.redhat.rhn.domain.common.CommonFactory; import com.redhat.rhn.domain.common.TinyUrl; import com.redhat.rhn.domain.kickstart.KickstartFactory; import com.redhat.rhn.domain.kickstart.KickstartSession; import com.redhat.rhn.domain.kickstart.KickstartSessionState; import com.redhat.rhn.domain.kickstart.KickstartableTree; import com.redhat.rhn.domain.org.Org; import com.redhat.rhn.domain.org.OrgFactory; import com.redhat.rhn.domain.rhnpackage.Package; import com.redhat.rhn.domain.rhnpackage.PackageFactory; import com.redhat.rhn.domain.rhnpackage.PackageSource; import com.redhat.rhn.domain.server.CrashFile; import com.redhat.rhn.domain.user.User; import com.redhat.rhn.domain.user.UserFactory; import com.redhat.rhn.frontend.action.kickstart.KickstartHelper; import com.redhat.rhn.frontend.struts.RhnHelper; import com.redhat.rhn.manager.channel.ChannelManager; import com.redhat.rhn.manager.download.DownloadManager; import com.redhat.rhn.manager.download.UnknownDownloadTypeException; import com.redhat.rhn.manager.kickstart.KickstartManager; import com.redhat.rhn.manager.system.CrashManager; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.actions.DownloadAction; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.RandomAccessFile; import java.net.URL; import java.net.URLConnection; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import java.util.regex.Pattern; import java.util.regex.Matcher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * ChannelPackagesAction * @version $Rev$ */ public class DownloadFile extends DownloadAction { private static Logger log = Logger.getLogger(DownloadFile.class); private static final String PARAMS = "params"; private static final String TYPE = "type"; private static final String HASH = "hash"; private static final String EXPIRE = "expire"; private static final String USERID = "userid"; private static final String FILEID = "fileid"; private static final String FILENAME = "filename"; private static final String CHILD = "child"; private static final String TREE = "tree"; private static final String SESSION = "session"; private static final String URL_STRING = "url"; private static final String CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; private static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain"; private static final String CONTENT_TYPE_TEXT_XML = "text/xml"; private static final Long DOWNLOAD_REPO_LOG_LENGTH = 102400L; private static final Long DOWNLOAD_REPO_LOG_MIN_LENGTH = 10L; /** {@inheritDoc} */ @Override public ActionForward execute(ActionMapping mapping, ActionForm formIn, HttpServletRequest request, HttpServletResponse response) throws Exception { String url = RhnHelper.getParameterWithSpecialCharacters(request, "url"); if (url == null) { return mapping.findForward("error"); } if (log.isDebugEnabled()) { log.debug("url : [" + url + "]"); } if (url.startsWith("/ty/")) { url = url.replaceFirst("/ty/", ""); String[] splits = url.split("/"); log.debug(splits[0]); TinyUrl ty = CommonFactory.lookupTinyUrl(splits[0]); url = url.replaceFirst(splits[0], ty.getUrl()); log.debug(url); } if (url.startsWith("/ks/dist")) { log.debug("URL is ks dist.."); ActionForward error = handleKickstartDownload(request, response, url, mapping); log.debug("Done handling ks download"); if (error != null) { log.debug("returning null"); return null; } } else if (url.startsWith("/cblr/svc/op/ks/")) { Map<String, Object> params = new HashMap<String, Object>(); params.put(TYPE, DownloadManager.DOWNLOAD_TYPE_COBBLER); params.put(URL_STRING, url); request.setAttribute(PARAMS, params); return super.execute(mapping, formIn, request, response); } else if (url.startsWith("/cobbler_api")) { Map<String, Object> params = new HashMap<String, Object>(); params.put(TYPE, DownloadManager.DOWNLOAD_TYPE_COBBLER_API); request.setAttribute(PARAMS, params); return super.execute(mapping, formIn, request, response); } else { ActionForward error = handleUserDownload(request, url, mapping); if (error != null) { return error; } } try { log.debug("Calling super.execute"); super.execute(mapping, formIn, request, response); } catch (Exception e) { e.printStackTrace(); log.error("Package retrieval error on file download url: " + url); return mapping.findForward("error"); } return null; } /** * Parse a /ks/dist url * The following URLS are accepted: * /ks/dist/tree-label/path/to/file.rpm * /ks/dist/org/#/tree-label/path/to/file * /ks/dist/session/HEX/tree-label/path/to/file.rpm * /ks/dist/tree-label/child/child-chan-label/path/to/file.rpm * * @param url the url to parse * @return a map with the following params: * label (req) * path (req) * session (opt) * orgId (opt) * child (opt) */ public static Map<String, String> parseDistUrl(String url) { Map<String, String> ret = new HashMap<String, String>(); if (url.charAt(0) == '/') { url = url.substring(1); } String[] split = url.split("/"); try { int labelPos = 2; if (split[2].equals("org")) { ret.put("orgId", split[3]); labelPos = 4; } else if (split[2].equals("session")) { ret.put("session", split[3]); labelPos = 4; } else if (split[2].equals("child")) { ret.put("child", split[3]); labelPos = 4; } ret.put("label", split[labelPos]); String path = ""; for (int i = labelPos + 1; i < split.length; i++) { path += "/" + split[i]; } ret.put("path", path); } catch (ArrayIndexOutOfBoundsException e) { return null; } return ret; } private ActionForward handleKickstartDownload(HttpServletRequest request, HttpServletResponse response, String url, ActionMapping mapping) throws IOException { if (log.isDebugEnabled()) { log.debug("URL : " + url); } Map<String, String> map = DownloadFile.parseDistUrl(url); if (map == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return mapping.findForward("error"); } String path = map.get("path"); String label = map.get("label"); Long orgId = null; if (map.containsKey("orgId")) { try { orgId = Long.parseLong(map.get("orgId")); } catch (NumberFormatException e) { // Do nothing } } KickstartSession ksession = null; KickstartSessionState newState = null; if (map.containsKey("session")) { String sessionId = SessionSwap.extractData(map.get("session"))[0]; ksession = KickstartFactory.lookupKickstartSessionById(new Long(sessionId)); } if (log.isDebugEnabled()) { log.debug("computed path to just the file: " + path); log.debug("Tree label to lookup: " + label); } KickstartableTree tree = null; if (orgId != null) { Org org = OrgFactory.lookupById(orgId); if (org != null) { tree = KickstartFactory.lookupKickstartTreeByLabel(label, org); } } else if (ksession != null) { tree = ksession.getKstree(); } else { tree = KickstartFactory.lookupKickstartTreeByLabel(label); } if (map.containsKey("child") && !Config.get().getBoolean("ks_restrict_child_channels")) { Channel child = ChannelFactory.lookupByLabel(map.get("child")); if (child == null || !child.getParentChannel().equals(tree.getChannel())) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return mapping.findForward("error"); } Map<String, Object> params = new HashMap<String, Object>(); params.put(TYPE, DownloadManager.DOWNLOAD_TYPE_KICKSTART); params.put(TREE, tree); params.put(CHILD, child); params.put(FILENAME, path); request.setAttribute(PARAMS, params); return null; } if (tree == null) { log.error("Tree not found."); response.sendError(HttpServletResponse.SC_NOT_FOUND); return mapping.findForward("error"); } Map<String, Object> params = new HashMap<String, Object>(); params.put(TYPE, DownloadManager.DOWNLOAD_TYPE_KICKSTART); params.put(TREE, tree); params.put(FILENAME, path); if (ksession != null) { params.put(SESSION, ksession); } request.setAttribute(PARAMS, params); return null; } private ActionForward handleUserDownload(HttpServletRequest request, String url, ActionMapping mapping) { List<String> split = Arrays.asList(url.split("/")); Iterator<String> it = split.iterator(); Map<String, Object> params = new HashMap<String, Object>(); String type = getNextValue(it); String hash = getNextValue(it); Long expire = new Long(getNextValue(it)); Long userId = new Long(getNextValue(it)); Long fileId = new Long(getNextValue(it)); String filename = getNextValue(it); params.put(TYPE, type); params.put(HASH, hash); params.put(EXPIRE, expire); params.put(USERID, userId); params.put(FILEID, fileId); params.put(FILENAME, filename); request.setAttribute(PARAMS, params); //If expire is at 0, then expiration is disabled for the download // we'll validate the SHA1 token to make sure someone didn't hack // it in the next step. if (expire != 0 && Calendar.getInstance().getTimeInMillis() > expire) { log.error("File download url has expired: " + url); return mapping.findForward("error"); } User user = UserFactory.lookupById(userId); if (!hash.equals(DownloadManager.getFileSHA1Token(fileId, filename, user, expire, type))) { log.error("Invalid hash on file download url: " + url); return mapping.findForward("error"); } return null; } @Override protected StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String path = ""; Map params = (Map) request.getAttribute(PARAMS); String type = (String) params.get(TYPE); if (type.equals(DownloadManager.DOWNLOAD_TYPE_KICKSTART)) { return getStreamInfoKickstart(mapping, form, request, response, path); } else if (type.equals(DownloadManager.DOWNLOAD_TYPE_COBBLER)) { String url = ConfigDefaults.get().getCobblerServerUrl() + (String) params.get(URL_STRING); KickstartHelper helper = new KickstartHelper(request); String data = ""; if (helper.isProxyRequest()) { data = KickstartManager.getInstance().renderKickstart(helper.getKickstartHost(), url); } else { data = KickstartManager.getInstance().renderKickstart(url); } setTextContentInfo(response, data.length()); return getStreamForText(data.getBytes()); } else if (type.equals(DownloadManager.DOWNLOAD_TYPE_COBBLER_API)) { // read data from POST body String postData = new String(); String line = null; BufferedReader reader = request.getReader(); while ((line = reader.readLine()) != null) { postData += line; } // Send data URL url = new URL(ConfigDefaults.get().getCobblerServerUrl() + "/cobbler_api"); URLConnection conn = url.openConnection(); conn.setDoOutput(true); OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); // this will write POST /download//cobbler_api instead of // POST /cobbler_api, but cobbler do not mind wr.write(postData, 0, postData.length()); wr.flush(); conn.connect(); // Get the response String output = new String(); BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); while ((line = rd.readLine()) != null) { output += line; } wr.close(); KickstartHelper helper = new KickstartHelper(request); if (helper.isProxyRequest()) { // Search/replacing all instances of cobbler host with host // we pass in, for use with Spacewalk Proxy. output = output.replaceAll(ConfigDefaults.get().getCobblerHost(), helper.getForwardedHost()); } setXmlContentInfo(response, output.length()); return getStreamForXml(output.getBytes()); } else { Long fileId = (Long) params.get(FILEID); Long userid = (Long) params.get(USERID); User user = UserFactory.lookupById(userid); if (type.equals(DownloadManager.DOWNLOAD_TYPE_PACKAGE)) { Package pack = PackageFactory.lookupByIdAndOrg(fileId, user.getOrg()); setBinaryContentInfo(response, pack.getPackageSize().intValue()); path = Config.get().getString(ConfigDefaults.MOUNT_POINT) + "/" + pack.getPath(); return getStreamForBinary(path); } else if (type.equals(DownloadManager.DOWNLOAD_TYPE_SOURCE)) { Package pack = PackageFactory.lookupByIdAndOrg(fileId, user.getOrg()); List<PackageSource> src = PackageFactory.lookupPackageSources(pack); if (!src.isEmpty()) { setBinaryContentInfo(response, src.get(0).getPackageSize().intValue()); path = Config.get().getString(ConfigDefaults.MOUNT_POINT) + "/" + src.get(0).getPath(); return getStreamForBinary(path); } } else if (type.equals(DownloadManager.DOWNLOAD_TYPE_REPO_LOG)) { Channel c = ChannelFactory.lookupById(fileId); ChannelManager.verifyChannelAdmin(user, fileId); StringBuilder output = new StringBuilder(); for (String fileName : ChannelManager.getLatestSyncLogFiles(c)) { RandomAccessFile file = new RandomAccessFile(fileName, "r"); long fileLength = file.length(); if (fileLength > DOWNLOAD_REPO_LOG_LENGTH) { file.seek(fileLength - DOWNLOAD_REPO_LOG_LENGTH); // throw away text till end of the actual line file.readLine(); } else { file.seek(0); } String line; while ((line = file.readLine()) != null) { output.append(line); output.append("\n"); } file.close(); if (output.length() > DOWNLOAD_REPO_LOG_MIN_LENGTH) { break; } } setTextContentInfo(response, output.length()); return getStreamForText(output.toString().getBytes()); } else if (type.equals(DownloadManager.DOWNLOAD_TYPE_CRASHFILE)) { CrashFile crashFile = CrashManager.lookupCrashFileByUserAndId(user, fileId); String crashPath = crashFile.getCrash().getStoragePath(); setBinaryContentInfo(response, (int) crashFile.getFilesize()); path = Config.get().getString(ConfigDefaults.MOUNT_POINT) + "/" + crashPath + "/" + crashFile.getFilename(); return getStreamForBinary(path); } } throw new UnknownDownloadTypeException( "The specified download type " + type + " is not currently supported"); } private StreamInfo getStreamInfoKickstart(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String path) throws Exception { Map params = (Map) request.getAttribute(PARAMS); path = (String) params.get(FILENAME); if (log.isDebugEnabled()) { log.debug("getStreamInfo KICKSTART type, path: " + path); } String diskPath = null; String kickstartMount = Config.get().getString(ConfigDefaults.MOUNT_POINT); String fileName; KickstartSession ksession = (KickstartSession) params.get(SESSION); KickstartSessionState newState = null; KickstartableTree tree = (KickstartableTree) params.get(TREE); Package rpmPackage = null; Channel child = (Channel) params.get(CHILD); if (tree.getBasePath().indexOf(kickstartMount) == 0) { log.debug("Trimming mount because tree is" + " explicitly rooted to the mount point"); kickstartMount = ""; } // If the tree is rooted somewhere other than // /var/satellite then no need to prepend it. if (tree.getBasePath().startsWith("/")) { log.debug("Tree isnt rooted at /var/satellite, lets just use basepath"); kickstartMount = ""; } // Searching for RPM if (path.endsWith(".rpm")) { String[] split = StringUtils.split(path, '/'); fileName = split[split.length - 1]; if (log.isDebugEnabled()) { log.debug("RPM filename: " + fileName); } Channel channel = tree.getChannel(); if (child != null) { channel = child; } rpmPackage = ChannelFactory.lookupPackageByFilename(channel, fileName); if (rpmPackage != null) { diskPath = Config.get().getString(ConfigDefaults.MOUNT_POINT) + "/" + rpmPackage.getPath(); if (log.isDebugEnabled()) { log.debug("found package :: diskPath path: " + diskPath); } newState = KickstartFactory.lookupSessionStateByLabel(KickstartSessionState.IN_PROGRESS); } else { if (log.isDebugEnabled()) { log.debug("Package was not in channel, looking in distro path."); } } } // either it's not an rpm, or we didn't find it in the channel // check for dir pings, virt manager or install, bz #345721 if (diskPath == null) { // my $dp = File::Spec->catfile($kickstart_mount, $tree->base_path, $path); if (child == null) { diskPath = kickstartMount + "/" + tree.getBasePath() + path; } else if (path.endsWith("/comps.xml")) { diskPath = Config.get().getString(ConfigDefaults.MOUNT_POINT) + "/" + child.getComps().getRelativeFilename(); } else { String[] split = StringUtils.split(path, '/'); if (split[0].equals("repodata")) { split[0] = child.getLabel(); } diskPath = "/var/cache/" + Config.get().getString("repomd_path_prefix", "rhn/repodata/") + "/" + StringUtils.join(split, '/'); } if (log.isDebugEnabled()) { log.debug("DirCheck path: " + diskPath); } File actualFile = new File(diskPath); if (actualFile.exists() && actualFile.isDirectory()) { log.debug("Directory hit. just return 200"); response.setContentLength(0); response.setStatus(HttpServletResponse.SC_OK); return getStreamForText("".getBytes()); } else if (actualFile.exists()) { log.debug("Looks like it is an actual file and it exists."); newState = KickstartFactory.lookupSessionStateByLabel(KickstartSessionState.STARTED); } else { log.error(diskPath + " Not Found .. 404!"); response.sendError(HttpServletResponse.SC_NOT_FOUND); return getStreamForText("".getBytes()); } } if (log.isDebugEnabled()) { log.debug("Final path before returning getStreamForBinary(): " + diskPath); } if (log.isDebugEnabled()) { Enumeration e = request.getHeaderNames(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); log.debug("header: [" + name + "]: " + request.getHeader(name)); } } if (request.getMethod().equals("HEAD")) { log.debug("Method is HEAD .. serving checksum"); return manualServeChecksum(response, rpmPackage, diskPath); } else if (request.getHeader("Range") != null) { log.debug("range detected. serving chunk of file"); String range = request.getHeader("Range"); return manualServeByteRange(request, response, diskPath, range); } // Update kickstart session if (ksession != null) { ksession.setState(newState); if (ksession.getPackageFetchCount() == null) { ksession.setPackageFetchCount(new Long(0)); } if (ksession.getState().getLabel().equals(KickstartSessionState.IN_PROGRESS)) { log.debug("Incrementing counter."); ksession.setPackageFetchCount(ksession.getPackageFetchCount().longValue() + 1); ksession.setLastFileRequest(path); } log.debug("Saving session."); KickstartFactory.saveKickstartSession(ksession); } log.debug("returning getStreamForBinary"); File actualFile = new File(diskPath); Date mtime = new Date(actualFile.lastModified()); SimpleDateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); formatter.setTimeZone(TimeZone.getTimeZone("GMT")); setBinaryContentInfo(response, (int) actualFile.length()); response.addHeader("last-modified", formatter.format(mtime)); log.debug("added last-modified and content-length values"); return getStreamForBinary(diskPath); } private void setBinaryContentInfo(HttpServletResponse responseIn, int lengthIn) { // make sure content type is set first!!! // otherwise content length gets ignored responseIn.setContentType(CONTENT_TYPE_OCTET_STREAM); responseIn.setContentLength(lengthIn); } private void setTextContentInfo(HttpServletResponse responseIn, int lengthIn) { // make sure content type is set first!!! // otherwise content length gets ignored responseIn.setContentType(CONTENT_TYPE_TEXT_PLAIN); responseIn.setContentLength(lengthIn); } private void setXmlContentInfo(HttpServletResponse responseIn, int lengthIn) { // make sure content type is set first!!! // otherwise content length gets ignored responseIn.setContentType(CONTENT_TYPE_TEXT_XML); responseIn.setContentLength(lengthIn); } private StreamInfo getStreamForText(byte[] text) { ByteArrayStreamInfo stream = new ByteArrayStreamInfo("text/plain", text); return stream; } private StreamInfo getStreamForXml(byte[] text) { ByteArrayStreamInfo stream = new ByteArrayStreamInfo(CONTENT_TYPE_TEXT_XML, text); return stream; } private StreamInfo getStreamForBinary(String path) { File file = new File(path); FileStreamInfo stream = new FileStreamInfo(CONTENT_TYPE_OCTET_STREAM, file); return stream; } private String getNextValue(Iterator<String> it) { while (it.hasNext()) { String next = it.next(); if (!StringUtils.isEmpty(next)) { return next; } } return null; } // Ported from perl - needed for proxy support private StreamInfo manualServeChecksum(HttpServletResponse response, Package rpmPackage, String diskPath) throws IOException { response.setContentType(CONTENT_TYPE_OCTET_STREAM); String checksum; // Obtain the checksum for the file in question and stick it in the // outgoing HTTP headers under "X-RHN-Checksum". if (rpmPackage != null && rpmPackage.getChecksum() != null && rpmPackage.getChecksum().getChecksum() != null) { checksum = rpmPackage.getChecksum().getChecksum(); response.setHeader("Content-Length", String.valueOf(rpmPackage.getPackageSize())); } else { File f = new File(diskPath); if (!f.exists()) { log.error("manualServeChecksum :: File not found: " + diskPath); response.sendError(HttpServletResponse.SC_NOT_FOUND); return getStreamForText(new byte[0]); } checksum = MD5Sum.getFileMD5Sum(f); response.setHeader("Content-Length", String.valueOf(f.length())); } // Create some headers. response.addHeader("X-RHN-Checksum", checksum); response.setStatus(HttpServletResponse.SC_OK); return getStreamForText("".getBytes()); } // Ported from perl - needed for yum's requests for byte ranges private StreamInfo manualServeByteRange(HttpServletRequest request, HttpServletResponse response, String diskPath, String range) { // bytes=440-25183 Pattern rangePattern = Pattern.compile("bytes=(\\d*)-(\\d*)"); Matcher rangeMatcher = rangePattern.matcher(range); if (!rangeMatcher.matches()) { // this will fail rangeMatcher.group(1); } long start, end; if (StringUtils.isEmpty(rangeMatcher.group(1))) { start = 0; } else { start = Long.valueOf(rangeMatcher.group(1)); } File actualFile = new File(diskPath); long totalSize = actualFile.length(); if (StringUtils.isEmpty(rangeMatcher.group(2))) { end = totalSize; } else { end = Long.valueOf(rangeMatcher.group(2)).longValue(); } if (log.isDebugEnabled()) { log.debug("manualServeByteRange Start : " + start); log.debug("manualServeByteRange End : " + end); } long size = end - start + 1; if (log.isDebugEnabled()) { log.debug("manualServeByteRange totalsize: " + totalSize); } if (size <= 0) { return getStreamForBinary(diskPath); } setBinaryContentInfo(response, (int) size); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); Date mtime = new Date(actualFile.lastModified()); // "EEE, dd MMM yyyy HH:mm:ss zzz"; SimpleDateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); formatter.setTimeZone(TimeZone.getTimeZone("GMT")); String fdate = formatter.format(mtime); response.addHeader("last-modified", fdate); response.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + totalSize); response.addHeader("Accept-Ranges", "bytes"); if (log.isDebugEnabled()) { log.debug("Added header last-modified: " + fdate); log.debug("Added header Content-Length: " + String.valueOf(size)); log.debug("Added header Content-Range: " + "bytes " + start + "-" + end + "/" + totalSize); log.debug("Added header Accept-Ranges: bytes"); } // gotta make sure it is end + 1 byte[] chunk = FileUtils.readByteArrayFromFile(actualFile, start, end + 1); if (log.isDebugEnabled()) { log.debug("chunk size: " + chunk.length); log.debug("read chunk into byte array. returning ByteArrayStreamInfo"); } ByteArrayStreamInfo stream = new ByteArrayStreamInfo(CONTENT_TYPE_OCTET_STREAM, chunk); return stream; } }