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.Exceptions;
25 import hunt.text.Common;
26 import hunt.util.StringBuilder;
27 import hunt.text.StringTokenizer;
28 
29 import hunt.collection.List;
30 import hunt.collection.ArrayList;
31 
32 import std.conv;
33 import std.format;
34 import std.string;
35 
36 
37 /**
38  * Byte range inclusive of end points.
39  * <PRE>
40  * parses the following types of byte ranges:
41  * bytes=100-499
42  * bytes=-300
43  * bytes=100-
44  * bytes=1-2,2-3,6-,-2
45  * given an entity length, converts range to string
46  * bytes 100-499/500
47  * </PRE>
48  * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
49  * 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.
50  *
51  * @version $version$
52  */
53 class InclusiveByteRange {
54     
55 
56     long first = 0;
57     long last = 0;
58 
59     this(long first, long last) {
60         this.first = first;
61         this.last = last;
62     }
63 
64     long getFirst() {
65         return first;
66     }
67 
68     long getLast() {
69         return last;
70     }
71 
72     /**
73      * @param headers Enumeration of Range header fields.
74      * @param size    Size of the resource.
75      * @return LazyList of satisfiable ranges
76      */
77     static List!InclusiveByteRange satisfiableRanges(List!string headers, long size) {
78         Object satRanges = null;
79 
80         // walk through all Range headers
81         bool isContinue = false;
82         do{
83             isContinue = false;
84             foreach (string header ; headers) {
85                 StringTokenizer tok = new StringTokenizer(header, "=,", false);
86                 string t = null;
87                 try {
88                     // read all byte ranges for this header 
89                     while (tok.hasMoreTokens()) {
90                         try {
91                             t = tok.nextToken().strip();
92 
93                             long first = -1;
94                             long last = -1;
95                             int d = cast(int)t.indexOf("-");
96                             if (d < 0 || t.indexOf("-", d + 1) >= 0) {
97                                 if ("bytes" == (t))
98                                     continue;
99                                 warningf("Bad range format: %s", t);
100                                 isContinue = true;
101                                 break;
102                             } else if (d == 0) {
103                                 if (d + 1 < t.length)
104                                     last = to!long(t.substring(d + 1).strip());
105                                 else {
106                                     warningf("Bad range format: %s", t);
107                                     continue;
108                                 }
109                             } else if (d + 1 < t.length) {
110                                 first = to!long(t.substring(0, d).strip());
111                                 last = to!long(t.substring(d + 1).strip());
112                             } else
113                                 first = to!long(t.substring(0, d).strip());
114 
115                             if (first == -1 && last == -1)
116                             {
117                                 isContinue = true;
118                                 break;
119                             }
120 
121                             if (first != -1 && last != -1 && (first > last))
122                             {
123                                 isContinue = true;
124                                 break;
125                             }
126 
127                             if (first < size) {
128                                 InclusiveByteRange range = new InclusiveByteRange(first, last);
129                                 satRanges = LazyList.add(satRanges, range);
130                             }
131                         } catch (NumberFormatException e) {
132                             warningf("Bad range format: %s", t);
133                             continue;
134                         }
135                     }
136                 } catch (Exception e) {
137                     warningf("Bad range format: %s", t);
138                 }
139             }
140 
141 
142         } while(isContinue);
143         return LazyList.getList!(InclusiveByteRange)(satRanges, true);
144     }
145 
146     long getFirst(long size) {
147         if (first < 0) {
148             long tf = size - last;
149             if (tf < 0)
150                 tf = 0;
151             return tf;
152         }
153         return first;
154     }
155 
156     long getLast(long size) {
157         if (first < 0)
158             return size - 1;
159 
160         if (last < 0 || last >= size)
161             return size - 1;
162         return last;
163     }
164 
165     long getSize(long size) {
166         return getLast(size) - getFirst(size) + 1;
167     }
168 
169     string toHeaderRangeString(long size) {
170         StringBuilder sb = new StringBuilder(40);
171         sb.append("bytes ");
172         sb.append(getFirst(size).to!string);
173         sb.append('-');
174         sb.append(getLast(size).to!string);
175         sb.append("/");
176         sb.append(to!string(size));
177         return sb.toString();
178     }
179 
180     static string to416HeaderRangeString(long size) {
181         StringBuilder sb = new StringBuilder(40);
182         sb.append("bytes */");
183         sb.append(to!string(size));
184         return sb.toString();
185     }
186 
187     override
188     string toString() {
189         return format("%d:%d", first, last);
190     }
191 
192 }
193 
194 
195