1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
4 //  ------------------------------------------------------------------------
5 //  All rights reserved. This program and the accompanying materials
6 //  are made available under the terms of the Eclipse Public License v1.0
7 //  and Apache License v2.0 which accompanies this distribution.
8 //
9 //      The Eclipse Public License is available at
10 //      http://www.eclipse.org/legal/epl-v10.html
11 //
12 //      The Apache License v2.0 is available at
13 //      http://www.opensource.org/licenses/apache2.0.php
14 //
15 //  You may elect to redistribute this code under either of these licenses.
16 //  ========================================================================
17 //
18 
19 module hunt.http.codec.http.model.InclusiveByteRange;
20 
21 
22 import hunt.logging;
23 
24 import hunt.lang.exception;
25 import hunt.string;
26 
27 import hunt.container.List;
28 import hunt.container.ArrayList;
29 
30 import std.conv;
31 import std.format;
32 import std.string;
33 
34 
35 /**
36  * Byte range inclusive of end points.
37  * <PRE>
38  * parses the following types of byte ranges:
39  * bytes=100-499
40  * bytes=-300
41  * bytes=100-
42  * bytes=1-2,2-3,6-,-2
43  * given an entity length, converts range to string
44  * bytes 100-499/500
45  * </PRE>
46  * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
47  * And yes the spec does strangely say that while 10-20, is bytes 10 to 20 and 10- is bytes 10 until the end that -20 IS NOT bytes 0-20, but the last 20 bytes of the content.
48  *
49  * @version $version$
50  */
51 class InclusiveByteRange {
52     
53 
54     long first = 0;
55     long last = 0;
56 
57     this(long first, long last) {
58         this.first = first;
59         this.last = last;
60     }
61 
62     long getFirst() {
63         return first;
64     }
65 
66     long getLast() {
67         return last;
68     }
69 
70     /**
71      * @param headers Enumeration of Range header fields.
72      * @param size    Size of the resource.
73      * @return LazyList of satisfiable ranges
74      */
75     static List!InclusiveByteRange satisfiableRanges(List!string headers, long size) {
76         Object satRanges = null;
77 
78         // walk through all Range headers
79         bool isContinue = false;
80         do{
81             isContinue = false;
82             foreach (string header ; headers) {
83                 StringTokenizer tok = new StringTokenizer(header, "=,", false);
84                 string t = null;
85                 try {
86                     // read all byte ranges for this header 
87                     while (tok.hasMoreTokens()) {
88                         try {
89                             t = tok.nextToken().strip();
90 
91                             long first = -1;
92                             long last = -1;
93                             int d = cast(int)t.indexOf("-");
94                             if (d < 0 || t.indexOf("-", d + 1) >= 0) {
95                                 if ("bytes" == (t))
96                                     continue;
97                                 warningf("Bad range format: %s", t);
98                                 isContinue = true;
99                                 break;
100                             } else if (d == 0) {
101                                 if (d + 1 < t.length)
102                                     last = to!long(t.substring(d + 1).strip());
103                                 else {
104                                     warningf("Bad range format: %s", t);
105                                     continue;
106                                 }
107                             } else if (d + 1 < t.length) {
108                                 first = to!long(t.substring(0, d).strip());
109                                 last = to!long(t.substring(d + 1).strip());
110                             } else
111                                 first = to!long(t.substring(0, d).strip());
112 
113                             if (first == -1 && last == -1)
114                             {
115                                 isContinue = true;
116                                 break;
117                             }
118 
119                             if (first != -1 && last != -1 && (first > last))
120                             {
121                                 isContinue = true;
122                                 break;
123                             }
124 
125                             if (first < size) {
126                                 InclusiveByteRange range = new InclusiveByteRange(first, last);
127                                 satRanges = LazyList.add(satRanges, range);
128                             }
129                         } catch (NumberFormatException e) {
130                             warningf("Bad range format: %s", t);
131                             continue;
132                         }
133                     }
134                 } catch (Exception e) {
135                     warningf("Bad range format: %s", t);
136                 }
137             }
138 
139 
140         } while(isContinue);
141         return LazyList.getList!(InclusiveByteRange)(satRanges, true);
142     }
143 
144     long getFirst(long size) {
145         if (first < 0) {
146             long tf = size - last;
147             if (tf < 0)
148                 tf = 0;
149             return tf;
150         }
151         return first;
152     }
153 
154     long getLast(long size) {
155         if (first < 0)
156             return size - 1;
157 
158         if (last < 0 || last >= size)
159             return size - 1;
160         return last;
161     }
162 
163     long getSize(long size) {
164         return getLast(size) - getFirst(size) + 1;
165     }
166 
167     string toHeaderRangeString(long size) {
168         StringBuilder sb = new StringBuilder(40);
169         sb.append("bytes ");
170         sb.append(getFirst(size).to!string);
171         sb.append('-');
172         sb.append(getLast(size).to!string);
173         sb.append("/");
174         sb.append(to!string(size));
175         return sb.toString();
176     }
177 
178     static string to416HeaderRangeString(long size) {
179         StringBuilder sb = new StringBuilder(40);
180         sb.append("bytes */");
181         sb.append(to!string(size));
182         return sb.toString();
183     }
184 
185     override
186     string toString() {
187         return format("%d:%d", first, last);
188     }
189 
190 }
191 
192 
193