Напишем простой анонимайзер ссылок на UniGUI с ограничением срока действия ссылок. Для начала сделаем простой вариант – с опорой на API стороннего сервиса. Наша задача – лишь прикрутить к такой ссылке ограничение по времени. Естественно, если мы используем сторонний сервис, то умные люди просто скопируют анонимизированную ссылку после перехода. Но можно и загружать весь контент с подменой ссылок – и для большинства простых сайтов этот способ будет работать. Но пока не будем усложнять и сделаем всё на API стороннего сервиса. Цель данного поста – разобраться – как вообще это можно сделать на UniGUI.
Вот как будет выглядеть программа.
Логин форма
Главная форма
Промежуточная форма – для случайно зашедших
Начали!
Логин форма
По умолчанию, в UniGUI загружается Логин-форма, если она существует. Либо, можно поставить ей параметр Visible:=True в объектном инспекторе и тогда она будет показана первой.
Итак, если пользователь набирает в адресной строке
1 |
http://localhost:8077/?showloginform=true |
,то показываем ему логин форму, в OnShow пропишем следующее
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
procedure TfEncryptLink_LoginForm.UniLoginFormShow(Sender: TObject); begin if UniApplication.Parameters.Values['showloginform']<>'true' then begin Hide; fHelloForm.show; //ShowMessage('Hello. Access denied. Try open login form'); end else begin CreateCaptcha; CenterElements; end; end; |
То есть, если такого параметра нет – мы прячем логин форму от случайного пользователя.
Как показать скрыть пароль на логин-форме?
1 2 3 4 5 6 7 8 9 10 |
procedure TfEncryptLink_LoginForm.cbShowPasswordClick(Sender: TObject); begin if cbShowPassword.Checked then UniEdit_Password.PasswordChar:=#0 else UniEdit_Password.PasswordChar:='*'; end; |
Как добавить CAPTCHA к логин форме?
Отдельная статья по добавлению CAPTCHA
Как обработать кнопку Enter?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
procedure TfEncryptLink_LoginForm.bEnterClick(Sender: TObject); begin // Emtyness Check if (UniEdit_Password.Text='') then begin ShowMessage('Empty Password Field'); Exit; end; if (UniEdit_Captcha.Text='') then begin ShowMessage('Empty Captcha Field'); Exit; end; //Password check if not (UniEdit_Password.Text=FCorrectPassword) then begin ShowMessage('Wrong Password'); Exit; end; // CAPTCHA CHECK if not (FNumberInCAPTCHA.ToString=UniEdit_Captcha.Text) then begin ShowMessage('Wrong CAPTCHA CODE'); Exit; end; Self.ModalResult:=mrOk; Close; fEncryptLinkApp.Visible:=True; end; |
Здесь, в принципе, все очевидно. Последовательно проверяем поля. Ну и соответственно, задаем корректный пароль
1 2 3 4 5 6 |
procedure TfEncryptLink_LoginForm.UniLoginFormCreate(Sender: TObject); begin FCorrectPassword:='CorrectPassword'; end; |
Центрирование элементов и создание CAPTCHA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
procedure TfEncryptLink_LoginForm.CenterElements; begin // UniEdit_Password.Left:=Trunc( Self.Width/2 - UniEdit_Password.Width/2 ); UniEdit_Captcha.Left:=Trunc( Self.Width/2 - UniEdit_Captcha.Width/2 ); bEnter.Left:=Trunc( Self.Width/2 - bEnter.Width/2 ); UniPanel_Captcha.Left:=Trunc( Self.Width/2 - UniPanel_Captcha.Width/2 ); lEnterPassword.Left:=UniPanel_Captcha.Left; lEnterCaptcha.Left:=UniPanel_Captcha.Left; cbShowPassword.Left:=UniPanel_Captcha.Left; end; procedure TfEncryptLink_LoginForm.CreateCaptcha; begin FNumberInCAPTCHA:=random(1000000); UniCanvas_Captcha.Bitmap:= nil; CreateCaptchaCore( UniCanvas_Captcha, inttostr(FNumberInCAPTCHA), 5 ); end; |
Update CAPTCHA
1 2 3 4 |
procedure TfEncryptLink_LoginForm.bUpdateCaptchaClick(Sender: TObject); begin CreateCaptcha; end; |
Главная форма
Собственно здесь мы шифруем нашу исходную ссылку
1. Делаем всевозможные проверки – на пустоту, есть ли уже такие зашифрованные параметры. Параллельно выделяем GET параметры, если они были
2. Добавляем наши параметры – использовать ли сторонний сервис? Использовать ли ограничение по времени?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
procedure TfEncryptLinkApp.bEncryptLinkClick(Sender: TObject); var OriginLink:string; NoBlockMeParam:string; EncryptedNoBlockMeParam:string; TimeLimitParam:string; EncryptedTimeLimitParam:string; ParamsToAdd:string; ParamsThatWereInLink: string; SplittedArrayAmpersand:TArray<string>; SplittedArrayEqual:TArray<string>; Position:integer; i: Integer; s: string; DecryptedValue: string; OriginLinkLowered: string; StartDateTimeParam: string; IsExitFromHere:boolean; OriginLinkParam: string; begin //-----------------------------1 STEP Checks // eEncryptedLink.Text:=''; eEncryptedLink.Clear; OriginLink:=( (eOriginLink.Text) ); // Check if params already there... if OriginLink.Contains('?') then begin Position:=Pos('?',OriginLink); ParamsThatWereInLink:=OriginLink.Substring(Position); ShowMessage(ParamsThatWereInLink); SplittedArrayAmpersand:=ParamsThatWereInLink.Split(['&']); for i := Low(SplittedArrayAmpersand) to High(SplittedArrayAmpersand) do begin // if ( SplittedArrayAmpersand[i].Contains('noblockmeparam') ) or ( SplittedArrayAmpersand[i].Contains('timelimitparam') ) or ( SplittedArrayAmpersand[i].Contains('startdatetimeparam')) then begin IsExitFromHere:=true; ShowMessage('Clear Link from NoBlockMeParam or TimeLimitParam or both'); Break; end; // if Params were Encrypted //DecryptedValue:=IdDecoderXXE.DecodeString(SplittedArrayAmpersand[i]); // dividing noblockmeparam=somevalue on [noblockmeparam,somevalue] SplittedArrayEqual:=SplittedArrayAmpersand[i].Split(['=']); if Length(SplittedArrayEqual)>0 then begin DecryptedValue:=IdDecoderXXE.DecodeString( SplittedArrayEqual[0] ); if (DecryptedValue.Contains('noblockmeparam')) or (DecryptedValue.Contains('timelimitparam')) or (DecryptedValue.Contains('startdatetimeparam')) then begin IsExitFromHere:=true; eEncryptedLink.Text:=''; eEncryptedLink.Clear; ShowMessage('Clear Link from Encrypted NoBlockMeParam or TimeLimitParam or both'+ #13#10+'just delete all encrypted symbols after ? sign'); Break; // Exit; end; end; end; end; if IsExitFromHere then Exit; // To not to Destroy Ecryption lets lower case here, after decrypt ! OriginLinkLowered:=AnsiLowerCase( (eOriginLink.Text) ); //Does it contain http:// or https:// ? if not (OriginLinkLowered.Contains('http://')) and not (OriginLinkLowered.Contains('https://')) then begin ShowMessage('Give Link with http:// or https://'); exit; end; // Is Emty Origin Link ? if OriginLinkLowered.IsEmpty then begin ShowMessage('Origin Link is Empty'); exit; end; //---------------------------------------ADDING PARAMS-------------------- //Adding OriginLinkParam OriginLinkParam:= IdEncoderXXE.Encode('originlinkparam')+ '='+ IdEncoderXXE.Encode(OriginLink); ParamsToAdd:=ParamsToAdd+OriginLinkParam; //Adding TimeLimitParam if cbTimeLimitation.Checked=true then begin StartDateTimeParam:= IdEncoderXXE.Encode('startdatetimeparam')+ '='+ IdEncoderXXE.Encode(DateTimeTostr(Now)); TimeLimitParam:= IdEncoderXXE.Encode('timelimitparam')+ '='+ IdEncoderXXE.Encode(seTimeLimitation.Value.ToString()); ParamsToAdd:=ParamsToAdd+'&'+StartDateTimeParam+'&'+TimeLimitParam; end; if cbUseNoBlockMe.Checked=true then begin NoBlockMeParam:= IdEncoderXXE.Encode('noblockmeparam')+ '='+ IdEncoderXXE.Encode('true'); //NoBlockMeParam:=IdEncoderXXE.Encode(NoBlockMeParam); ParamsToAdd:=ParamsToAdd+'&'+NoBlockMeParam end; { // Previous Variant //Adding ParamsToAdd to OriginLink if ( OriginLink.Contains('?') ) // and last symbol<>? and ( ( OriginLink.Substring(OriginLink.Length-1)<>('?') )) then OriginLink:=OriginLink+'&'+ParamsToAdd else if ( OriginLink.Contains('?') ) // and last symbol=? and ( ( OriginLink.Substring(OriginLink.Length-1)=('?') )) then OriginLink:=OriginLink+ParamsToAdd else OriginLink:=OriginLink+'?'+ParamsToAdd; } // ShowMessage(UniSession.URL); // eEncryptedLink.Text:=OriginLink; if ParamsThatWereInLink<>'' then eEncryptedLink.Text:=UniSession.URL+'?'+ParamsThatWereInLink+'&'+ParamsToAdd else eEncryptedLink.Text:=UniSession.URL+'?'+ParamsToAdd end; |
В принципе ничего сложного, но было пара моментов с особенностями самого UniGUI.
Промежуточная HELLO форма
Цель данной формы – поздороваться со случайным прохожим либо сделать редирект на нужный ресурс, если в GET параметрах мы нашли необходимое…
Здесь все основное действие будет происходить в AfterSHow, потому что в OnSHow Redirect работал некорректно…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
procedure TfHelloForm.UniFormAfterShow(Sender: TObject); var noblockmeparam,timelimitparam, startdatetimeparam,originlinkparam:string; HoursLimit:integer; Startdatetime:TDateTime; AnonymizedLink: string; // OriginLink:string; begin // In Encrypted Link we have encoded param names, so we should repeat it here // to parse them in the link noblockmeparam:='noblockmeparam'; timelimitparam:='timelimitparam'; startdatetimeparam:='startdatetimeparam'; originlinkparam:='originlinkparam'; noblockmeparam:=IdEncoderXXE.Encode(noblockmeparam); timelimitparam:=IdEncoderXXE.Encode(timelimitparam); startdatetimeparam:=IdEncoderXXE.Encode(startdatetimeparam); originlinkparam:=IdEncoderXXE.Encode(originlinkparam); // if noblockmeparam in params... if UniApplication.Parameters.Values[noblockmeparam]=IdEncoderXXE.Encode('true') then begin //Getting Origin Link FOriginLink:= IdDecoderXXE.DecodeString( UniApplication.Parameters.Values[originlinkparam] ); AnonymizedLink:=AnonymizeLink(FOriginLink); //----------Check if Link Expired // ShowMessage(OriginLink); if UniApplication.Parameters.Values[timelimitparam]<>'' then HoursLimit:= IdDecoderXXE.DecodeString( UniApplication.Parameters.Values[timelimitparam] ).ToInteger; if UniApplication.Parameters.Values[startdatetimeparam]<>'' then Startdatetime:= StrTodateTime( IdDecoderXXE.DecodeString( UniApplication.Parameters.Values[startdatetimeparam] ) ); //-----------------Check Time Expired if ( UniApplication.Parameters.Values[startdatetimeparam]<>'' ) and (UniApplication.Parameters.Values[timelimitparam]<>'') and ((UniApplication.Parameters.Values[originlinkparam]<>'')) then begin if HoursBetween(Now,Startdatetime)<HoursLimit then begin lStatus.Caption:='Link Expired'; ShowMessage('Link Expired'); end else begin lStatus.Caption:='All Ok. Link not Expired. Redirecting'; UniSession.UrlRedirect(AnonymizedLink); end; end else //---if No Check Time Expired then just redirecting begin UniSession.UrlRedirect(AnonymizedLink); end; end; end; |
Тестируем
Закриптуем ссылку данного блога
Теперь перейдем по ней в браузере, предварительно изменив условие проверки времени на следующее
1 |
if HoursBetween(Now,Startdatetime)<HoursLimit |
Это сильно облегчит проверку кода. Вот что мы увидим
Теперь попробуем изменить это условие обратно и мы видим переход по анонимизированной ссылке. Можно было, конечно ей довесить наши криптованные параметры, но это бессмысленно, так как сервер уже не наш, в данном случае.
Выводы
Все работает. Ограничение данного метода в том, что ограничение по времени, да и любое другое, будет работать ровно до того момента, пока пользователь не догадается скопировать ссылку после редиректа.
Но это можно обойти – загрузив контент на свой сайт, как это я делал в других статьях. Здесь, конечно встает другое ограничение – если на сайте много скриптов, то не все они могут и будут работать корректно. А в случае с анонимайзером – такой проблемы нет.