1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2005-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20%% 21%% Description: This module impements handling of ftp server responses. 22 23-module(ftp_response). 24 25%% Internal API 26-export([parse_lines/3, interpret/1, error_string/1]). 27 28-include("ftp_internal.hrl"). 29 30%% First group of reply code digits 31-define(POS_PREL, 1). 32-define(POS_COMPL, 2). 33-define(POS_INTERM, 3). 34-define(TRANS_NEG_COMPL, 4). 35-define(PERM_NEG_COMPL, 5). 36%% Second group of reply code digits 37-define(SYNTAX,0). 38-define(INFORMATION,1). 39-define(CONNECTION,2). 40-define(AUTH_ACC,3). 41-define(UNSPEC,4). 42-define(FILE_SYSTEM,5). 43 44%%%========================================================================= 45%%% INTERNAL API 46%%%========================================================================= 47 48%%-------------------------------------------------------------------------- 49%% parse_lines(Data, AccLines, StatusCode) -> {ok, Lines} | 50%% {continue, {Data, 51%% AccLines, StatusCode}} 52%% 53%% Data = binary() - data recived on the control connection from the 54%% ftp-server. 55%% AccLines = [string()] 56%% StatusCode = start | {byte(), byte(), byte()} | finish - 57%% Indicates where in the parsing process we are. 58%% start - (looking for the status code of the message) 59%% {byte(), byte(), byte()} - status code found, now 60%% looking for the last line indication. 61%% finish - now on the last line. 62%% Description: Parses a ftp control response message. 63%% "A reply is defined to contain the 3-digit code, followed by Space 64%% <SP>, followed by one line of text (where some maximum line length 65%% has been specified), and terminated by the Telnet end-of-line 66%% code (CRLF), or a so called multilined reply for example: 67%% 68%% 123-First line 69%% Second line 70%% 234 A line beginning with numbers 71%% 123 The last line 72%% 73%% The user-process then simply needs to search for the second 74%% occurrence of the same reply code, followed by <SP> (Space), at 75%% the beginning of a line, and ignore all intermediary lines. If 76%% an intermediary line begins with a 3-digit number, the Server 77%% will pad the front to avoid confusion. 78%%-------------------------------------------------------------------------- 79 80%% Make sure we received the first 4 bytes so we know how to parse 81%% the FTP server response e.i. is the response composed of one 82%% or multiple lines. 83parse_lines(Bin, Lines, start) when size(Bin) < 4 -> 84 {continue, {Bin, Lines, start}}; 85%% Multiple lines exist 86parse_lines(<<C1, C2, C3, $-, Rest/binary>>, Lines, start) -> 87 parse_lines(Rest, [$-, C3, C2, C1 | Lines], {C1, C2, C3}); 88%% Only one line exists 89parse_lines(<<C1, C2, C3, ?WHITE_SPACE, Bin/binary>>, Lines, start) -> 90 parse_lines(Bin, [?WHITE_SPACE, C3, C2, C1 | Lines], finish); 91 92%% Last line found 93parse_lines(<<?CR, ?LF, C1, C2, C3, ?WHITE_SPACE, Rest/binary>>, Lines, {C1, C2, C3}) -> 94 parse_lines(Rest, [?WHITE_SPACE, C3, C2, C1, ?LF, ?CR | Lines], finish); 95%% Potential end found wait for more data 96parse_lines(<<?CR, ?LF, C1, C2, C3>> = Bin, Lines, {C1, C2, C3}) -> 97 {continue, {Bin, Lines, {C1, C2, C3}}}; 98%% Intermidate line begining with status code 99parse_lines(<<?CR, ?LF, C1, C2, C3, Rest/binary>>, Lines, {C1, C2, C3}) -> 100 parse_lines(Rest, [C3, C2, C1, ?LF, ?CR | Lines], {C1, C2, C3}); 101 102%% Potential last line wait for more data 103parse_lines(<<?CR, ?LF, C1, C2>> = Data, Lines, {C1, C2, _} = StatusCode) -> 104 {continue, {Data, Lines, StatusCode}}; 105parse_lines(<<?CR, ?LF, C1>> = Data, Lines, {C1, _, _} = StatusCode) -> 106 {continue, {Data, Lines, StatusCode}}; 107parse_lines(<<?CR, ?LF>> = Data, Lines, {_,_,_} = StatusCode) -> 108 {continue, {Data, Lines, StatusCode}}; 109parse_lines(<<?LF>> = Data, Lines, {_,_,_} = StatusCode) -> 110 {continue, {Data, Lines, StatusCode}}; 111parse_lines(<<>> = Data, Lines, {_,_,_} = StatusCode) -> 112 {continue, {Data, Lines, StatusCode}}; 113%% Part of the multiple lines 114parse_lines(<<Octet, Rest/binary>>, Lines, {_,_, _} = StatusCode) -> 115 parse_lines(Rest, [Octet | Lines], StatusCode); 116 117%% End of FTP server response found 118parse_lines(<<?CR, ?LF>>, Lines, finish) -> 119 {ok, lists:reverse([?LF, ?CR | Lines]), <<>>}; 120parse_lines(<<?CR, ?LF, Rest/binary>>, Lines, finish) -> 121 {ok, lists:reverse([?LF, ?CR | Lines]), Rest}; 122 123%% Potential end found wait for more data 124parse_lines(<<?CR>> = Data, Lines, finish) -> 125 {continue, {Data, Lines, finish}}; 126parse_lines(<<>> = Data, Lines, finish) -> 127 {continue, {Data, Lines, finish}}; 128%% Part of last line 129parse_lines(<<Octet, Rest/binary>>, Lines, finish) -> 130 parse_lines(Rest, [Octet | Lines], finish). 131 132%%-------------------------------------------------------------------------- 133%% interpret(Lines) -> {Status, Text} 134%% Lines = [byte(), byte(), byte() | Text] - ftp server response as 135%% returned by parse_lines/3 136%% Stauts = atom() (see interpret_status/3) 137%% Text = [string()] 138%% 139%% Description: Create nicer data to match on. 140%%-------------------------------------------------------------------------- 141interpret([Didgit1, Didgit2, Didgit3 | Data]) -> 142 Code1 = Didgit1 - $0, 143 Code2 = Didgit2 - $0, 144 Code3 = Didgit3 - $0, 145 {interpret_status(Code1, Code2, Code3), Data}. 146 147%%-------------------------------------------------------------------------- 148%% error_string(Error) -> string() 149%% Error = {error, term()} | term() 150%% 151%% Description: Translates error codes into strings intended for 152%% human interpretation. 153%%-------------------------------------------------------------------------- 154error_string({error, Reason}) -> 155 error_string(Reason); 156 157error_string(echunk) -> "Synchronisation error during chunk sending."; 158error_string(eclosed) -> "Session has been closed."; 159error_string(econn) -> "Connection to remote server prematurely closed."; 160error_string(eexists) ->"File or directory already exists."; 161error_string(ehost) -> "Host not found, FTP server not found, " 162 "or connection rejected."; 163error_string(elogin) -> "User not logged in."; 164error_string(enotbinary) -> "Term is not a binary."; 165error_string(epath) -> "No such file or directory, already exists, " 166 "or permission denied."; 167error_string(etype) -> "No such type."; 168error_string(euser) -> "User name or password not valid."; 169error_string(etnospc) -> "Insufficient storage space in system."; 170error_string(enofile) -> "No files found or file unavailable"; 171error_string(epnospc) -> "Exceeded storage allocation " 172 "(for current directory or dataset)."; 173error_string(efnamena) -> "File name not allowed."; 174error_string(Reason) -> 175 lists:flatten(io_lib:format("Unknown error: ~w", [Reason])). 176 177%%%======================================================================== 178%%% Internal functions 179%%%======================================================================== 180 181%% Positive Preleminary Reply 182interpret_status(?POS_PREL,_,_) -> pos_prel; 183%%FIXME ??? 3??? interpret_status(?POS_COMPL, ?AUTH_ACC, 3) -> tls_upgrade; 184interpret_status(?POS_COMPL, ?AUTH_ACC, 4) -> tls_upgrade; 185%% Positive Completion Reply 186interpret_status(?POS_COMPL,_,_) -> pos_compl; 187%% Positive Intermediate Reply nedd account 188interpret_status(?POS_INTERM,?AUTH_ACC,2) -> pos_interm_acct; 189%% Positive Intermediate Reply 190interpret_status(?POS_INTERM,_,_) -> pos_interm; 191%% No files found or file not available 192interpret_status(?TRANS_NEG_COMPL,?FILE_SYSTEM,0) -> enofile; 193%% No storage area no action taken 194interpret_status(?TRANS_NEG_COMPL,?FILE_SYSTEM,2) -> etnospc; 195%% Temporary Error, no action taken 196interpret_status(?TRANS_NEG_COMPL,_,_) -> trans_neg_compl; 197%% Permanent disk space error, the user shall not try again 198interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,0) -> epath; 199interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,2) -> epnospc; 200interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,3) -> efnamena; 201interpret_status(?PERM_NEG_COMPL,?AUTH_ACC,0) -> elogin; 202interpret_status(?PERM_NEG_COMPL,_,_) -> perm_neg_compl. 203 204