|
1 | | -/** |
2 | | - * Licensed under the Apache License, Version 2.0 (the "License"); |
3 | | - * you may not use this file except in compliance with the License. |
4 | | - * You may obtain a copy of the License at |
5 | | - * |
6 | | - * http://www.apache.org/licenses/LICENSE-2.0 |
7 | | - * |
8 | | - * Unless required by applicable law or agreed to in writing, software |
9 | | - * distributed under the License is distributed on an "AS IS" BASIS, |
10 | | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
11 | | - * See the License for the specific language governing permissions and |
12 | | - * limitations under the License. |
13 | | - * |
14 | | - * Copyright 2014 Edgar Espina |
15 | | - */ |
16 | 1 | package io.jooby; |
17 | 2 |
|
18 | 3 | public class ByteRange { |
19 | | - |
20 | 4 | private static final String BYTES_EQ = "bytes="; |
21 | 5 |
|
22 | | - public static final ByteRange NO_RANGE = new ByteRange(-1, -1) { |
23 | | - @Override public ByteRange apply(Context ctx, long contentLength) { |
24 | | - return new ByteRange(0, contentLength); |
25 | | - } |
26 | | - }; |
| 6 | + private String value; |
27 | 7 |
|
28 | | - public static final ByteRange NOT_SATISFIABLE = new ByteRange(-1, -1) { |
29 | | - @Override public ByteRange apply(Context ctx, long contentLength) { |
30 | | - throw new Err(StatusCode.REQUESTED_RANGE_NOT_SATISFIABLE); |
31 | | - } |
32 | | - }; |
| 8 | + private long start; |
| 9 | + |
| 10 | + private long end; |
33 | 11 |
|
34 | | - public final long start; |
| 12 | + private long contentLength; |
35 | 13 |
|
36 | | - public final long end; |
| 14 | + private String contentRange; |
37 | 15 |
|
38 | | - private ByteRange(long start, long end) { |
| 16 | + private StatusCode statusCode; |
| 17 | + |
| 18 | + private ByteRange(String value, long start, long end, long contentLength, String contentRange, |
| 19 | + StatusCode statusCode) { |
| 20 | + this.value = value; |
39 | 21 | this.start = start; |
40 | 22 | this.end = end; |
| 23 | + this.contentLength = contentLength; |
| 24 | + this.contentRange = contentRange; |
| 25 | + this.statusCode = statusCode; |
41 | 26 | } |
42 | 27 |
|
43 | | - public ByteRange apply(Context ctx, long contentLength) { |
44 | | - long start = this.start; |
45 | | - long end = this.end; |
46 | | - if (start == -1) { |
47 | | - start = contentLength - end; |
48 | | - end = contentLength - 1; |
49 | | - } |
50 | | - if (end == -1 || end > contentLength - 1) { |
51 | | - end = contentLength - 1; |
52 | | - } |
53 | | - if (start > end) { |
54 | | - return NOT_SATISFIABLE; |
| 28 | + public long getStart() { |
| 29 | + return start; |
| 30 | + } |
| 31 | + |
| 32 | + public long getEnd() { |
| 33 | + return end; |
| 34 | + } |
| 35 | + |
| 36 | + public long getContentLength() { |
| 37 | + return contentLength; |
| 38 | + } |
| 39 | + |
| 40 | + public String getContentRange() { |
| 41 | + return contentRange; |
| 42 | + } |
| 43 | + |
| 44 | + public StatusCode getStatusCode() { |
| 45 | + return statusCode; |
| 46 | + } |
| 47 | + |
| 48 | + public boolean isPartial() { |
| 49 | + return statusCode == StatusCode.PARTIAL_CONTENT; |
| 50 | + } |
| 51 | + |
| 52 | + public ByteRange apply(Context ctx) { |
| 53 | + if (statusCode == StatusCode.REQUESTED_RANGE_NOT_SATISFIABLE) { |
| 54 | + // Is throwing the right choice? Probably better to just send the status code and skip error |
| 55 | + throw new Err(statusCode, value); |
| 56 | + } else if (statusCode == StatusCode.PARTIAL_CONTENT) { |
| 57 | + ctx.setHeader("Accept-Ranges", "bytes"); |
| 58 | + ctx.setHeader("Content-Range", contentRange); |
| 59 | + ctx.setContentLength(contentLength); |
| 60 | + ctx.setStatusCode(statusCode); |
55 | 61 | } |
56 | | - // offset |
57 | | - long limit = (end - start + 1); |
58 | | - ctx.setHeader("Accept-Ranges", "bytes"); |
59 | | - ctx.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + contentLength); |
60 | | - ctx.setHeader("Content-Length", limit); |
61 | | - ctx.setStatusCode(StatusCode.PARTIAL_CONTENT); |
62 | | - return new ByteRange(start, limit); |
| 62 | + return this; |
63 | 63 | } |
64 | 64 |
|
65 | | - public boolean valid() { |
66 | | - return this != NO_RANGE && this != NOT_SATISFIABLE; |
| 65 | + @Override public String toString() { |
| 66 | + return value; |
67 | 67 | } |
68 | 68 |
|
69 | | - public static ByteRange parse(String value) { |
70 | | - if (value == null) { |
71 | | - return NO_RANGE; |
| 69 | + public static ByteRange parse(String value, long contentLength) { |
| 70 | + if (contentLength <= 0 || value == null) { |
| 71 | + // NOOP |
| 72 | + return new ByteRange(value, 0, contentLength, contentLength, "bytes */" + contentLength, |
| 73 | + StatusCode.OK); |
72 | 74 | } |
| 75 | + |
73 | 76 | if (!value.startsWith(BYTES_EQ)) { |
74 | | - return NOT_SATISFIABLE; |
| 77 | + return new ByteRange(value, 0, 0, contentLength, "bytes */" + contentLength, |
| 78 | + StatusCode.REQUESTED_RANGE_NOT_SATISFIABLE); |
75 | 79 | } |
| 80 | + |
76 | 81 | try { |
77 | 82 | long[] range = {-1, -1}; |
78 | 83 | int r = 0; |
79 | 84 | int len = value.length(); |
80 | 85 | int i = BYTES_EQ.length(); |
81 | | - int start = i; |
| 86 | + int offset = i; |
82 | 87 | char ch; |
83 | 88 | // Only Single Byte Range Requests: |
84 | 89 | while (i < len && (ch = value.charAt(i)) != ',') { |
85 | 90 | if (ch == '-') { |
86 | | - if (start < i) { |
87 | | - range[r] = Long.parseLong(value.substring(start, i).trim()); |
| 91 | + if (offset < i) { |
| 92 | + range[r] = Long.parseLong(value.substring(offset, i).trim()); |
88 | 93 | } |
89 | | - start = i + 1; |
| 94 | + offset = i + 1; |
90 | 95 | r += 1; |
91 | 96 | } |
92 | 97 | i += 1; |
93 | 98 | } |
94 | | - if (start < i) { |
| 99 | + if (offset < i) { |
95 | 100 | if (r == 0) { |
96 | | - return NOT_SATISFIABLE; |
| 101 | + return new ByteRange(value, 0, 0, contentLength, "bytes */" + contentLength, |
| 102 | + StatusCode.REQUESTED_RANGE_NOT_SATISFIABLE); |
97 | 103 | } |
98 | | - range[r++] = Long.parseLong(value.substring(start, i).trim()); |
| 104 | + range[r++] = Long.parseLong(value.substring(offset, i).trim()); |
99 | 105 | } |
100 | 106 | if (r == 0 || (range[0] == -1 && range[1] == -1)) { |
101 | | - return NOT_SATISFIABLE; |
| 107 | + return new ByteRange(value, 0, 0, contentLength, "bytes */" + contentLength, |
| 108 | + StatusCode.REQUESTED_RANGE_NOT_SATISFIABLE); |
| 109 | + } |
| 110 | + |
| 111 | + long start = range[0]; |
| 112 | + long end = range[1]; |
| 113 | + if (start == -1) { |
| 114 | + start = contentLength - end; |
| 115 | + end = contentLength - 1; |
| 116 | + } |
| 117 | + if (end == -1 || end > contentLength - 1) { |
| 118 | + end = contentLength - 1; |
| 119 | + } |
| 120 | + if (start > end) { |
| 121 | + return new ByteRange(value, 0, 0, contentLength, "bytes */" + contentLength, |
| 122 | + StatusCode.REQUESTED_RANGE_NOT_SATISFIABLE); |
102 | 123 | } |
103 | | - return new ByteRange(range[0], range[1]); |
104 | | - } catch (NumberFormatException x) { |
105 | | - return NOT_SATISFIABLE; |
| 124 | + // offset |
| 125 | + long limit = (end - start + 1); |
| 126 | + return new ByteRange(value, start, limit, limit, |
| 127 | + "bytes " + start + "-" + end + "/" + contentLength, StatusCode.PARTIAL_CONTENT); |
| 128 | + } catch (NumberFormatException expected) { |
| 129 | + return new ByteRange(value, 0, 0, contentLength, "bytes */" + contentLength, |
| 130 | + StatusCode.REQUESTED_RANGE_NOT_SATISFIABLE); |
106 | 131 | } |
107 | 132 | } |
108 | 133 | } |
0 commit comments