package org.limewire.http; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import org.apache.http.Header; import org.apache.http.HttpException; import org.apache.http.protocol.HttpContext; /** * Parses ranges found in Range headers formatted as specified in RFC 2616, * 14.35.1. */ public class RangeHeaderInterceptor implements HeaderInterceptor { public static String RANGE_HEADER = "Range"; private List<Range> ranges; /** * Looks for range header of form, "Range: bytes=", "Range: bytes=", "Range: * bytes ", etc. Note that the "=" is required by HTTP, but old versions of * BearShare do not send it. The value following the bytes unit will be in * the form '-n', 'm-n', or 'm-'. * * @see #getRequestedRanges() */ public void process(Header header, HttpContext context) throws HttpException, IOException { if (!RANGE_HEADER.equals(header.getName())) { return; } String value = header.getValue().trim(); if (!value.startsWith("bytes")) { throw new MalformedHeaderException( "bytes not present in range header"); } if (value.length() <= 6) { throw new MalformedHeaderException( "range not present in range header"); } // remove the "bytes" or "bytes=" value = value.substring(6); StringTokenizer t = new StringTokenizer(value, ","); while (t.hasMoreElements()) { Range range = parseRange(t.nextToken().trim()); if (this.ranges == null) { this.ranges = new ArrayList<Range>(1); } this.ranges.add(range); } } private Range parseRange(String value) throws MalformedHeaderException { if (value.length() < 2) { throw new MalformedHeaderException("invalid range: " + value); } final Range range = new Range(); int i = value.indexOf("-"); if (i == -1 || value.indexOf("-", i + 1) != -1) { // there must be exactly one dash throw new MalformedHeaderException("invalid range: " + value); } else if (i == 0) { // - n try { range.endOffset = Long.parseLong(value.substring(1).trim()); } catch (NumberFormatException e) { throw new MalformedHeaderException(); } } else if (i == value.length() - 1) { // n - try { range.startOffset = Long.parseLong(value.substring(0, value.length() - 1).trim()); } catch (NumberFormatException e) { throw new MalformedHeaderException(); } } else { // n-m try { range.startOffset = Long .parseLong(value.substring(0, i).trim()); } catch (NumberFormatException e) { throw new MalformedHeaderException(); } try { range.endOffset = Long.parseLong(value.substring(i + 1).trim()); } catch (NumberFormatException e) { throw new MalformedHeaderException(); } } if (range.endOffset != -1 && range.startOffset > range.endOffset) { throw new MalformedHeaderException( "start offset is greater than end offset (" + range.startOffset + ">" + range.endOffset + ")"); } assert range.startOffset >= 0 || range.endOffset >= 0; return range; } public boolean hasRequestedRanges() { return ranges != null; } /** * List of ranges found in all Range headers. * * @return null, if no Range headers were found */ public Range[] getRequestedRanges() { return (ranges != null) ? ranges.toArray(new Range[0]) : null; } /** * A single byte range. */ public static class Range { private long startOffset = -1; private long endOffset = -1; /* For testing. */ protected Range(long startOffset, long endOffset) { this.startOffset = startOffset; this.endOffset = endOffset; } private Range() { } /** * Returns the inclusive start offset. * * @param totalSize the total size of the entity */ public long getStartOffset(long totalSize) { if (totalSize < 0) { throw new IllegalArgumentException("totalSize must be >= 0"); } if (startOffset > totalSize - 1) { return -1; } if (startOffset >= 0) { return startOffset; } // format is -n, meaning return last n bytes if (totalSize >= endOffset) { return totalSize - endOffset; } else { // requested bytes exceed size of entity, return the whole // entity return 0; } } /** * Returns the inclusive end offset. * * @param totalSize the total size of the entity */ public long getEndOffset(long totalSize) { if (totalSize < 0) { throw new IllegalArgumentException("totalSize must be >= 0"); } if (startOffset >= 0) { if (endOffset >= 0 && endOffset < totalSize) { return endOffset; } else { return totalSize - 1; } } // format is -n, meaning return last n bytes return totalSize - 1; } } }