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