ProfileImporter
UT2003 :: Object >> Actor >> Info >> InternetInfo >> InternetLink >> TcpLink >> ProfileImporter (Ladder1.46)
00001 //----------------------------------------------------------- 00002 // Ladder.ProfileImporter 00003 // 00004 // Handles all communication with foreign servers containing 00005 // exported profile information 00006 // Stores remote profile URL locations 00007 //----------------------------------------------------------- 00008 class ProfileImporter extends TcpLink config(LadderProfiles); 00009 00010 const LOGTAG = 'ProfileImport'; 00011 00012 var enum EImportResult 00013 { 00014 IMPORT_BadURL, // Incorrect URL format 00015 IMPORT_ResolveError, // Couldn't resolve URL 00016 IMPORT_BindError, // Couldn't bind port 00017 IMPORT_InvalidProfile, // File was not a profile 00018 IMPORT_TimeOut, // Timed out waiting 00019 IMPORT_ServerError, // Unspecified Foreign Host Error 00020 IMPORT_BadRequest, // Receieved 400 Error 00021 IMPORT_Unauthorized, // Receieved 401 Error 00022 IMPORT_Forbidden, // Receieved 403 Error 00023 IMPORT_NotFound, // Receieved 404 Error 00024 IMPORT_Success // Profile Successfully Imported 00025 } ImportResult; 00026 00027 var bool bWaiting; //Waiting for ImportResult 00028 00029 struct ZipLocation 00030 { 00031 var string ProfileURL; 00032 var string ZipName; 00033 var string ZipURL; 00034 }; 00035 00036 var IpAddr ServerIP; 00037 var LadderGameRules LadderRules; 00038 var ProfileConfigSet RemoteProfile; 00039 var LadderQuery QueryPage; 00040 00041 var Session ImportS; 00042 var WebRequest QueryRequest; 00043 var WebResponse QueryResponse; 00044 00045 var string Buffer; 00046 var string Header; 00047 var string LF; // Line Feed 00048 var string CR; // Carriage Return 00049 var string Token; // Token Delimiter 00050 00051 var string ProfileServerAddress; 00052 var string RemoteProfilePath; 00053 00054 // Localized strings 00055 var localized string BeginImportText; 00056 00057 // Localized error messages 00058 var localized string ErrorText; 00059 var localized string ErrorURLText; 00060 var localized string ErrorResolvingText; 00061 var localized string ErrorBindingText; 00062 var localized string ErrorTimeoutText; 00063 var localized string ErrorFNFText; 00064 var localized string ErrorBadRequestText; 00065 var localized string ErrorUnauthorizedText; 00066 var localized string ErrorForbiddenText; 00067 var localized string ErrorNoSession; 00068 var localized string ErrorNoBuffer; 00069 00070 var localized string BadImportURLText; 00071 00072 var config array<ZipLocation> RemoteZips; // Remote profile zips locations 00073 00074 function PostBeginPlay() 00075 { 00076 Super.PostBeginPlay(); 00077 Disable('Tick'); 00078 00079 LF = Chr(10); 00080 CR = Chr(13); 00081 Token = Chr(21); 00082 } 00083 00084 function Tick(float DeltaTime) 00085 { 00086 Super.Tick(DeltaTime); 00087 00088 if (!bWaiting) 00089 { 00090 // Finish importing profile data, let LadderQuery finish sending page 00091 QueryPage.RemoteImport(Self); 00092 Disable('Tick'); 00093 } 00094 } 00095 00096 function Timer() 00097 { 00098 log(ErrorTimeoutText @ ProfileServerAddress,LOGTAG); 00099 ImportResult = IMPORT_Timeout; 00100 bWaiting = False; 00101 00102 // Manually close the connection since 00103 // we didn't receive a response 00104 Close(); 00105 } 00106 00107 // Called from the Management Portal page 00108 function bool Connect(string ProfileURL, Session TempS) 00109 { 00110 local string L, R; 00111 00112 bWaiting = True; 00113 Enable('Tick'); 00114 00115 log( BeginImportText @ ProfileURL, LOGTAG); 00116 if ( Left(ProfileURL, 7) ~= "http://") 00117 ProfileServerAddress = ParseDelimited(ProfileURL,"/",3); 00118 else 00119 { 00120 log( ErrorURLText @ "\"http://\"!", LOGTAG); 00121 ImportResult = IMPORT_BadURL; 00122 bWaiting = False; 00123 return false; 00124 } 00125 00126 ServerIP.Port = 80; 00127 if (InStr(ProfileServerAddress, ":") != -1) 00128 { 00129 Divide(ProfileServerAddress, ":", L, R); 00130 ProfileServerAddress = L; 00131 ServerIP.Port = int(R); 00132 } 00133 00134 RemoteProfilePath = "/" $ ParseDelimited(ProfileURL,"/",4,True); 00135 00136 Buffer = ""; 00137 SetTimer(20.0, False); 00138 ImportS = TempS; 00139 Resolve( ProfileServerAddress ); 00140 return true; 00141 } 00142 00143 event ResolveFailed() 00144 { 00145 log( ErrorResolvingText @ ProfileServerAddress, LOGTAG); 00146 ImportResult = IMPORT_ResolveError; 00147 bWaiting = False; 00148 } 00149 00150 event Resolved(IpAddr NumericIP) 00151 { 00152 if( NumericIP.Addr == 0 ) 00153 { 00154 log( ErrorResolvingText @ ProfileServerAddress, LOGTAG); 00155 ImportResult = IMPORT_ResolveError; 00156 bWaiting = False; 00157 return; 00158 } 00159 ServerIP.Addr = NumericIP.Addr; 00160 00161 // Bind the local port. 00162 if( BindPort() == 0 ) 00163 { 00164 log( ErrorBindingText, LOGTAG); 00165 ImportResult = IMPORT_BindError; 00166 bWaiting = False; 00167 return; 00168 } 00169 // Everything went OK - proceed to open connection 00170 Open( ServerIP ); 00171 } 00172 00173 event Opened() 00174 { 00175 if (LinkMode != MODE_Line) 00176 LinkMode = MODE_Line; 00177 00178 SetTimer(0.0, False); // Stop timeout counter 00179 00180 // Check header of file first, to make sure it's a valid file 00181 RequestFile(0); 00182 } 00183 00184 // Connection was closed by foreign host - Check header for response code 00185 event Closed() 00186 { 00187 local string Result; 00188 00189 // First find header and respond accordingly 00190 if (SeperateHeaders(Buffer)) 00191 Result = ProcessHeaders(); 00192 else 00193 { 00194 ImportResult = IMPORT_ServerError; 00195 bWaiting = False; 00196 return; 00197 } 00198 00199 if (Result == "") 00200 { 00201 ImportResult = IMPORT_ServerError; 00202 bWaiting = False; 00203 return; 00204 } 00205 00206 /* Possible Response Codes: 00207 "200" ; OK 00208 "201" ; Created 00209 "202" ; Accepted 00210 "204" ; No Content 00211 "301" ; Moved Permanently 00212 "302" ; Moved Temporarily 00213 "304" ; Not Modified 00214 "400" ; Bad Request 00215 "401" ; Unauthorized 00216 "403" ; Forbidden 00217 "404" ; Not Found 00218 "500" ; Internal Server Error 00219 "501" ; Not Implemented 00220 "502" ; Bad Gateway 00221 "503" ; Service Unavailable 00222 */ 00223 switch (Result) 00224 { 00225 case "200": 00226 // Buffer should now only contain the actual document 00227 // Fill ImportS from document info 00228 if (FillSessionInfo()) 00229 ImportResult = IMPORT_Success; 00230 break; 00231 case "400": 00232 log( ErrorBadRequestText, LOGTAG); 00233 ImportResult = IMPORT_BadRequest; 00234 break; 00235 case "401": 00236 log( ErrorUnauthorizedText, LOGTAG); 00237 ImportResult = IMPORT_Unauthorized; 00238 break; 00239 case "403": 00240 log( ErrorForbiddenText, LOGTAG); 00241 ImportResult = IMPORT_Forbidden; 00242 break; 00243 default: 00244 log( ErrorFNFText, LOGTAG); 00245 ImportResult = IMPORT_NotFound; 00246 } 00247 bWaiting = False; 00248 } 00249 00250 function bool FillSessionInfo() 00251 { 00252 local string Line; 00253 local string Type; 00254 local string Info; 00255 local bool bValidProfile; 00256 00257 local string Token1, Token2, Token3, Token4, Token5, Token6, Token7; 00258 00259 if (ImportS == None) 00260 { 00261 log( ErrorNoSession, LOGTAG); 00262 ImportResult = IMPORT_ServerError; 00263 return false; 00264 } 00265 00266 if (Buffer == "") 00267 { 00268 log(ErrorNoBuffer, LOGTAG); 00269 ImportResult = IMPORT_ServerError; 00270 return False; 00271 } 00272 else 00273 { 00274 while ( (Left(Buffer,1) ~= CR) || (Left(Buffer,1) ~= LF) ) 00275 Buffer = Mid(Buffer,1); 00276 } 00277 00278 while (ReadBufferedLine(Line)) 00279 { 00280 Divide(Line,Chr(58),Type,Info); 00281 00282 Token1 = ParseDelimited(Info,Token,1); 00283 Token2 = ParseDelimited(Info,Token,2); 00284 Token3 = ParseDelimited(Info,Token,3); 00285 Token4 = ParseDelimited(Info,Token,4); 00286 Token5 = ParseDelimited(Info,Token,5); 00287 Token6 = ParseDelimited(Info,Token,6); 00288 Token7 = ParseDelimited(Info,Token,7); 00289 00290 switch (Type) 00291 { 00292 case "Profile": 00293 ImportS.setValue("ProfileName",Token1,True); 00294 ImportS.setValue("GameType",Token2,True); 00295 ImportS.setValue("MaxMaps",Token3,True); 00296 ImportS.setValue("GameName",Token4,True); 00297 ImportS.setValue("ACClass",Token5,True); 00298 if (Token6 != "") ImportS.setValue("CustomGameLoc",Token6,True); 00299 if (Token7 != "") ImportS.setValue("CustomACLoc",Token7,True); 00300 bValidProfile = True; 00301 break; 00302 00303 case "Map": 00304 ImportS.setMap(Token1,int(Token2),True,bool(Token3)); 00305 if (Token4 != "") AddRemoteZipLocation(Token1,Token4); 00306 break; 00307 00308 case "Mutator": 00309 ImportS.setMutator(Token2,True,bool(Token3)); 00310 if (Token4 != "") AddRemoteZipLocation(Token2,Token4); 00311 00312 case "Data": 00313 ImportS.setValue(Token1,Token2,True); 00314 break; 00315 } 00316 } 00317 00318 if (bValidProfile) 00319 return true; 00320 00321 ImportResult = IMPORT_InvalidProfile; 00322 return false; 00323 } 00324 00325 function AddRemoteZipLocation(string PackageName, string PackageLocation) 00326 { 00327 local int i; 00328 local bool ZipFound; 00329 00330 local ZipLocation Zip; 00331 00332 i = InStr(PackageName, "."); 00333 if (i != -1) 00334 PackageName = Left(PackageName,i); 00335 00336 Zip.ProfileURL = ProfileServerAddress $ RemoteProfilePath; 00337 Zip.ZipName = PackageName; 00338 Zip.ZipURL = PackageLocation; 00339 00340 for (i = 0; i < RemoteZips.Length; i++) 00341 { 00342 if (RemoteZips[i].ProfileURL ~= Zip.ProfileURL && RemoteZips[i].ZipName ~= Zip.ZipName) 00343 { 00344 ZipFound = True; 00345 break; 00346 } 00347 } 00348 00349 if (ZipFound) RemoteZips[i] = Zip; 00350 else RemoteZips[RemoteZips.Length] = Zip; 00351 00352 SaveConfig(); 00353 } 00354 00355 function string FindRemoteZipLocation(string PackageName, string ProfileURL, optional bool bAllowOtherLocations) 00356 { 00357 local int i; 00358 00359 i = InStr(PackageName, "."); 00360 if (i != -1) PackageName = Left(PackageName,i); 00361 00362 for (i = 0;i < RemoteZips.Length; i++) 00363 if ((RemoteZips[i].ZipName ~= PackageName) && ((RemoteZips[i].ProfileURL ~= ProfileURL) || (bAllowOtherLocations)) ) 00364 return RemoteZips[i].ZipURL; 00365 00366 return ""; 00367 } 00368 00369 function bool SeperateHeaders(string WebPage) 00370 { 00371 local int i; 00372 00373 //CR$LF$CR$LF seperates header and document 00374 i = InStr(WebPage, CR$LF$CR$LF); 00375 00376 if (i == -1) 00377 return false; 00378 00379 Header = Left(WebPage, i); 00380 Buffer = Mid(WebPage, i + 3); 00381 return true; 00382 } 00383 00384 function string ProcessHeaders() 00385 { 00386 if (Header == "") 00387 return ""; 00388 00389 //Remove all CR, leaving only LF 00390 Header = RemoveStr(Header,CR); 00391 00392 //Parse Response Code from server, ex. HTTP/1.1 200 OK 00393 return Mid(ParseDelimited(Header,LF,1),9,3); 00394 } 00395 00396 event ReceivedText(string Text) 00397 { 00398 Buffer = Buffer $ Text; 00399 } 00400 00401 event ReceivedLine(string Line) 00402 { 00403 local string Result; 00404 00405 if (SeperateHeaders(Line)) 00406 Result = ProcessHeaders(); 00407 00408 if (Result == "200") 00409 { 00410 Result = FindContentType(Line); 00411 if (Result ~= "text/plain") 00412 { 00413 RequestFile(1); 00414 LinkMode = Mode_Text; 00415 } 00416 else 00417 { 00418 ImportResult = IMPORT_InvalidProfile; 00419 bWaiting = False; 00420 } 00421 } 00422 00423 else 00424 { 00425 ImportResult = IMPORT_NotFound; 00426 bWaiting = False; 00427 } 00428 } 00429 00430 function string FindContentType(string Text) 00431 { 00432 local array<string> Lines; 00433 local string Tmp; 00434 local int i, j; 00435 00436 Buffer = Text; 00437 while (ReadBufferedLine(Tmp)) 00438 Lines[Lines.Length] = Tmp; 00439 00440 for (i=0;i<Lines.Length;i++) 00441 { 00442 if (Left(Lines[i],13) ~= "Content-Type:") 00443 { 00444 Tmp = Mid(Lines[i],14); 00445 j = InStr(Tmp,";"); 00446 if (j >= 0) 00447 Tmp = Left(Tmp,j); 00448 } 00449 } 00450 00451 return Tmp; 00452 } 00453 00454 function RequestFile(int RequestType) 00455 { 00456 local string RequestMethod, ControlText; 00457 00458 if (RequestType == 0) 00459 { 00460 RequestMethod = "HEAD"; 00461 ControlText = "Keep-Alive"; 00462 } 00463 else 00464 { 00465 RequestMethod = "GET"; 00466 ControlText = "close"; 00467 } 00468 00469 SendText(RequestMethod@RemoteProfilePath@"HTTP/1.1"); 00470 SendText("Host:"@ProfileServerAddress); 00471 SendText("Accept: text/plain"); // Accept only plain txt files 00472 SendText("User-agent: ProfileManager; UT2003"@Level.EngineVersion); 00473 SendText("Pragma: no-cache"); // Do not allow proxies to send cached information 00474 SendText("Cache-control: no-cache"); 00475 SendText("Connection:"@ControlText); // No further information to be sent 00476 SendText(""); 00477 } 00478 00479 function bool ReadBufferedLine(out string Text) 00480 { 00481 local int i; 00482 00483 i = InStr(Buffer, LF); 00484 00485 if(i == -1) 00486 return False; 00487 00488 if (Mid(Buffer, i-1, 1) == CR) 00489 Text = Left(Buffer, i-1); 00490 else Text = Left(Buffer, i); 00491 00492 Buffer = Mid(Buffer, i+1); 00493 return True; 00494 } 00495 00496 // Copied from BufferedTcpLink 00497 function string ParseDelimited(string Text, string Delimiter, int Count, optional bool bToEndOfLine) 00498 { 00499 local string Result; 00500 local int Found, i; 00501 local string s; 00502 00503 Result = ""; 00504 Found = 1; 00505 00506 for(i=0;i<Len(Text);i++) 00507 { 00508 s = Mid(Text, i, 1); 00509 if(InStr(Delimiter, s) != -1) 00510 { 00511 if(Found == Count) 00512 { 00513 if(bToEndOfLine) 00514 return Result$Mid(Text, i); 00515 else 00516 return Result; 00517 } 00518 00519 Found++; 00520 } 00521 else 00522 { 00523 if(Found >= Count) 00524 Result = Result $ s; 00525 } 00526 } 00527 00528 return Result; 00529 } 00530 00531 function string RemoveStr(string Source,string mask) 00532 { 00533 local int i; 00534 local string left,right; 00535 00536 i = InStr(Source,mask); 00537 while (i != -1) 00538 { 00539 Divide(Source,mask,left,right); 00540 Source = Left$Right; 00541 i = InStr(Source,Mask); 00542 } 00543 00544 return Source; 00545 }