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