|
| 1 | +{ |
| 2 | +Decode and encode the Bencode format used in torrent files |
| 3 | +
|
| 4 | +public domain code? |
| 5 | +Source code from: |
| 6 | +http://www.torry.net/quicksearchd.php?String=bencode&Title=Yes |
| 7 | +
|
| 8 | +With some minor modification made by Gerry Ferdinandus |
| 9 | +} |
| 10 | + |
| 11 | +{ |
| 12 | + Bug fix 2014-10-09 |
| 13 | + 'i' integer now accept negative number |
| 14 | + if not ((X in ['0'..'9' , '-']) or (X = 'e')) then |
| 15 | + raise Exception.Create(''); |
| 16 | +
|
| 17 | +} |
| 18 | + |
| 19 | +unit BEncode; |
| 20 | +{$mode objfpc}{$H+} |
| 21 | +interface |
| 22 | + |
| 23 | + |
| 24 | +uses |
| 25 | + Classes, Contnrs, SysUtils; |
| 26 | + |
| 27 | +type |
| 28 | + TBEncodedFormat = (befEmpty, befString, befInteger, befList, befDictionary); |
| 29 | + |
| 30 | + TBEncoded = class; |
| 31 | + |
| 32 | + TBEncodedData = class |
| 33 | + public |
| 34 | + Header: string; |
| 35 | + Data: TBEncoded; |
| 36 | + destructor Destroy; override; |
| 37 | + public |
| 38 | + constructor Create(Data_: TBEncoded); |
| 39 | + end; |
| 40 | + |
| 41 | + { TBEncodedDataList } |
| 42 | + |
| 43 | + TBEncodedDataList = class(TObjectList) |
| 44 | + protected |
| 45 | + function GetItems(Index: Integer): TBEncodedData; |
| 46 | + procedure SetItems(Index: Integer; AClass: TBEncodedData); |
| 47 | + public |
| 48 | + function FindElement(Header: string): TBEncoded; |
| 49 | + function Add(AClass: TBEncodedData): Integer; |
| 50 | + function Extract(Item: TBEncodedData): TBEncodedData; |
| 51 | + function Remove(AClass: TBEncodedData): Integer; |
| 52 | + function IndexOf(AClass: TBEncodedData): Integer; |
| 53 | + |
| 54 | + function RemoveElement(Header: string): integer;//2011-1030 |
| 55 | + |
| 56 | + function First: TBEncodedData; |
| 57 | + function Last: TBEncodedData; |
| 58 | + procedure Insert(Index: Integer; AClass: TBEncodedData); |
| 59 | + property Items[Index: Integer]: TBEncodedData read GetItems write SetItems; |
| 60 | + default; |
| 61 | + end; |
| 62 | + |
| 63 | + TBEncoded = class(TObject) |
| 64 | + private |
| 65 | + FFormat: TBEncodedFormat; |
| 66 | + procedure SetFormat(Format: TBEncodedFormat); |
| 67 | + public |
| 68 | + StringData: string; |
| 69 | + IntegerData: int64; |
| 70 | + ListData: TBEncodedDataList; |
| 71 | + property Format: TBEncodedFormat read FFormat write SetFormat; |
| 72 | + class procedure Encode(Encoded: TBEncoded; var Output: string); |
| 73 | + destructor Destroy; override; |
| 74 | + constructor Create(Stream: TStream); |
| 75 | + constructor Create; |
| 76 | + end; |
| 77 | + |
| 78 | +implementation |
| 79 | + |
| 80 | +destructor TBEncodedData.Destroy; |
| 81 | +begin |
| 82 | + Data.Free; |
| 83 | + inherited Destroy; |
| 84 | +end; |
| 85 | + |
| 86 | +constructor TBEncodedData.Create(Data_: TBEncoded); |
| 87 | +begin |
| 88 | + inherited Create; |
| 89 | + Self.Data := Data_; |
| 90 | +end; |
| 91 | + |
| 92 | +destructor TBEncoded.Destroy; |
| 93 | +begin |
| 94 | + if ListData <> nil then |
| 95 | + ListData.Free; |
| 96 | + |
| 97 | + inherited Destroy; |
| 98 | +end; |
| 99 | + |
| 100 | +constructor TBEncoded.Create(Stream: TStream); |
| 101 | + |
| 102 | + function GetString(Buffer: string): string; |
| 103 | + var |
| 104 | + X: char; |
| 105 | + begin |
| 106 | + // loop until we come across it |
| 107 | + X := ' '; |
| 108 | + repeat |
| 109 | + if Stream.Read(X, 1) <> 1 then |
| 110 | + raise Exception.Create(''); |
| 111 | + if not ((X in ['0'..'9']) or (x = ':')) then |
| 112 | + raise Exception.Create(''); |
| 113 | + if X = ':' then |
| 114 | + begin |
| 115 | + if Buffer = '' then |
| 116 | + raise Exception.Create(''); |
| 117 | + if Length(Buffer) > 6 then |
| 118 | + raise Exception.Create(''); |
| 119 | + SetLength(Result, StrToInt(Buffer)); |
| 120 | + if Stream.Read(Result[1], Length(Result)) <> Length(Result) then |
| 121 | + raise Exception.Create(''); |
| 122 | + Break; |
| 123 | + end |
| 124 | + else |
| 125 | + Buffer := Buffer + X; |
| 126 | + until False; |
| 127 | + end; |
| 128 | + |
| 129 | +var |
| 130 | + X: char; |
| 131 | + Buffer: string; |
| 132 | + Data: TBEncodedData; |
| 133 | + Encoded: TBEncoded; |
| 134 | +begin |
| 135 | + inherited Create; |
| 136 | + |
| 137 | + X := ' '; |
| 138 | + |
| 139 | + // get first character to determine the format of the proceeding data |
| 140 | + if Stream.Read(X, 1) <> 1 then |
| 141 | + raise Exception.Create(''); |
| 142 | + |
| 143 | + // is it an integer? |
| 144 | + if X = 'i' then |
| 145 | + begin |
| 146 | + // yes it is, let's read until we come across e |
| 147 | + Buffer := ''; |
| 148 | + repeat |
| 149 | + //must be able to read the stream |
| 150 | + if Stream.Read(X, 1) <> 1 then |
| 151 | + raise Exception.Create(''); |
| 152 | + |
| 153 | + //Must be a integer value or 'e' |
| 154 | + if not ((X in ['0'..'9' , '-']) or (X = 'e')) then |
| 155 | + raise Exception.Create(''); |
| 156 | + |
| 157 | + //if found the 'end' then decode it. |
| 158 | + if X = 'e' then |
| 159 | + begin |
| 160 | + if Buffer = '' then |
| 161 | + raise Exception.Create('') |
| 162 | + else |
| 163 | + begin |
| 164 | + Format := befInteger; |
| 165 | + IntegerData := StrToInt64(Buffer); |
| 166 | + Break; |
| 167 | + end; |
| 168 | + end |
| 169 | + else |
| 170 | + Buffer := Buffer + X; |
| 171 | + until False; |
| 172 | + end |
| 173 | + // is it a list? |
| 174 | + else if X = 'l' then |
| 175 | + begin |
| 176 | + // its a list |
| 177 | + Format := befList; |
| 178 | + |
| 179 | + // loop until we come across e |
| 180 | + repeat |
| 181 | + // have a peek around and see if theres an e |
| 182 | + if Stream.Read(X, 1) <> 1 then |
| 183 | + raise Exception.Create(''); |
| 184 | + // is it an e? |
| 185 | + if X = 'e' then |
| 186 | + Break; |
| 187 | + // otherwise move the cursor back |
| 188 | + Stream.Seek(-1, soFromCurrent); |
| 189 | + // create the element |
| 190 | + Encoded := TBEncoded.Create(Stream); |
| 191 | + // add it to the list |
| 192 | + ListData.Add(TBEncodedData.Create(Encoded)); |
| 193 | + until False; |
| 194 | + end |
| 195 | + // is it a dictionary? |
| 196 | + else if X = 'd' then |
| 197 | + begin |
| 198 | + // its a dictionary :> |
| 199 | + Format := befDictionary; |
| 200 | + |
| 201 | + // loop until we come across e |
| 202 | + repeat |
| 203 | + // have a peek around and see if theres an e |
| 204 | + if Stream.Read(X, 1) <> 1 then |
| 205 | + raise Exception.Create(''); |
| 206 | + // is it an e? |
| 207 | + if X = 'e' then |
| 208 | + Break; |
| 209 | + // if it isnt an e it has to be numerical! |
| 210 | + if not (X in ['0'..'9']) then |
| 211 | + raise Exception.Create(''); |
| 212 | + // now read the string data |
| 213 | + Buffer := GetString(string(X)); |
| 214 | + // create the element |
| 215 | + Encoded := TBEncoded.Create(Stream); |
| 216 | + // create the data element |
| 217 | + Data := TBEncodedData.Create(Encoded); |
| 218 | + Data.Header := Buffer; |
| 219 | + // add it to the list |
| 220 | + ListData.Add(Data); |
| 221 | + until False; |
| 222 | + end |
| 223 | + // is it a string? |
| 224 | + else if X in ['0'..'9'] then |
| 225 | + begin |
| 226 | + StringData := GetString(string(X)); |
| 227 | + Format := befString; |
| 228 | + end |
| 229 | + else |
| 230 | + raise Exception.Create(''); |
| 231 | +end; |
| 232 | + |
| 233 | +constructor TBEncoded.Create; |
| 234 | +begin |
| 235 | + inherited Create; |
| 236 | +end; |
| 237 | + |
| 238 | +class procedure TBEncoded.Encode(Encoded: TBEncoded; var Output: string); |
| 239 | +var |
| 240 | + i: integer; |
| 241 | +begin |
| 242 | + with Encoded do |
| 243 | + begin |
| 244 | + // what type of member is it? |
| 245 | + case Format of |
| 246 | + befString: Output := Output + IntToStr(Length(StringData)) + ':' + |
| 247 | + StringData; |
| 248 | + befInteger: Output := Output + 'i' + IntToStr(IntegerData) + 'e'; |
| 249 | + befList: |
| 250 | + begin |
| 251 | + Output := Output + 'l'; |
| 252 | + for i := 0 to ListData.Count - 1 do |
| 253 | + Encode(TBEncoded(ListData[i].Data), Output); |
| 254 | + Output := Output + 'e'; |
| 255 | + end; |
| 256 | + befDictionary: |
| 257 | + begin |
| 258 | + Output := Output + 'd'; |
| 259 | + for i := 0 to ListData.Count - 1 do |
| 260 | + begin |
| 261 | + Output := Output + IntToStr(Length(ListData[i].Header)) + ':' + |
| 262 | + ListData[i].Header; |
| 263 | + Encode(TBEncoded(ListData[i].Data), Output); |
| 264 | + end; |
| 265 | + Output := Output + 'e'; |
| 266 | + end; |
| 267 | + end; |
| 268 | + end; |
| 269 | +end; |
| 270 | + |
| 271 | +procedure TBEncoded.SetFormat(Format: TBEncodedFormat); |
| 272 | +begin |
| 273 | + if Format in [befList, befDictionary] then |
| 274 | + ListData := TBEncodedDataList.Create; |
| 275 | + FFormat := Format; |
| 276 | +end; |
| 277 | + |
| 278 | +function TBEncodedDataList.FindElement(Header: string): TBEncoded; |
| 279 | +var |
| 280 | + i: integer; |
| 281 | +begin |
| 282 | + Header := LowerCase(Header); |
| 283 | + for i := 0 to Count - 1 do |
| 284 | + if LowerCase(Items[i].Header) = Header then |
| 285 | + begin |
| 286 | + Result := Items[i].Data; |
| 287 | + Exit; |
| 288 | + end; |
| 289 | + |
| 290 | + Result := nil; |
| 291 | +end; |
| 292 | + |
| 293 | +function TBEncodedDataList.Add(AClass: TBEncodedData): Integer; |
| 294 | +begin |
| 295 | + Result := inherited Add(AClass); |
| 296 | +end; |
| 297 | + |
| 298 | +function TBEncodedDataList.Extract(Item: TBEncodedData): TBEncodedData; |
| 299 | +begin |
| 300 | + Result := TBEncodedData(inherited Extract(Item)); |
| 301 | +end; |
| 302 | + |
| 303 | +function TBEncodedDataList.First: TBEncodedData; |
| 304 | +begin |
| 305 | + Result := TBEncodedData(inherited First); |
| 306 | +end; |
| 307 | + |
| 308 | +function TBEncodedDataList.GetItems(Index: Integer): TBEncodedData; |
| 309 | +begin |
| 310 | + Result := TBEncodedData(inherited Items[Index]); |
| 311 | +end; |
| 312 | + |
| 313 | +function TBEncodedDataList.IndexOf(AClass: TBEncodedData): Integer; |
| 314 | +begin |
| 315 | + Result := inherited IndexOf(AClass); |
| 316 | +end; |
| 317 | + |
| 318 | +function TBEncodedDataList.RemoveElement(Header: string): Integer; |
| 319 | +var |
| 320 | + i: integer; |
| 321 | +begin |
| 322 | + Header := LowerCase(Header); |
| 323 | + for i := 0 to Count - 1 do |
| 324 | + if LowerCase(Items[i].Header) = Header then |
| 325 | + begin |
| 326 | + Result := i; |
| 327 | + Remove(Items[i]);//memory will be released. |
| 328 | + Exit; |
| 329 | + end; |
| 330 | + Result := -1; |
| 331 | +end; |
| 332 | + |
| 333 | +procedure TBEncodedDataList.Insert(Index: Integer; AClass: TBEncodedData); |
| 334 | +begin |
| 335 | + inherited Insert(Index, AClass); |
| 336 | +end; |
| 337 | + |
| 338 | +function TBEncodedDataList.Last: TBEncodedData; |
| 339 | +begin |
| 340 | + Result := TBEncodedData(inherited First); |
| 341 | +end; |
| 342 | + |
| 343 | +function TBEncodedDataList.Remove(AClass: TBEncodedData): Integer; |
| 344 | +begin |
| 345 | + Result := inherited Remove(AClass); |
| 346 | +end; |
| 347 | + |
| 348 | +procedure TBEncodedDataList.SetItems(Index: Integer; AClass: TBEncodedData); |
| 349 | +begin |
| 350 | + inherited Items[Index] := AClass; |
| 351 | +end; |
| 352 | + |
| 353 | +end. |
0 commit comments