Produced by Araxis Merge on 11/20/2017 2:16:16 PM GMT Standard Time. See www.araxis.com for information about Merge. This report uses XHTML and CSS2, and is best viewed with a modern standards-compliant browser. For optimum results when printing this report, use landscape orientation and enable printing of background images and colours in your browser.
# | Location | File | Last Modified |
---|---|---|---|
1 | C:\Merge Test Files\8.0.47\java\org\apache\catalina\authenticator | BasicAuthenticator.java | Fri Sep 29 16:53:28 2017 UTC |
2 | C:\Merge Test Files\8.5.23\java\org\apache\catalina\authenticator | BasicAuthenticator.java | Thu Sep 28 11:32:16 2017 UTC |
Description | Between Files 1 and 2 |
|
---|---|---|
Text Blocks | Lines | |
Unchanged | 5 | 508 |
Changed | 4 | 8 |
Inserted | 0 | 0 |
Removed | 0 | 0 |
Whitespace | Consecutive whitespace is treated as a single space |
---|---|
Character case | Differences in character case are significant |
Line endings | Differences in line endings (CR and LF characters) are ignored |
CR/LF characters | Not shown in the comparison detail |
No regular expressions were active.
1 | /* | 1 | /* | |||
2 | * License d to the A pache Soft ware Found ation (ASF ) under on e or more | 2 | * License d to the A pache Soft ware Found ation (ASF ) under on e or more | |||
3 | * contrib utor licen se agreeme nts. See the NOTICE file dist ributed wi th | 3 | * contrib utor licen se agreeme nts. See the NOTICE file dist ributed wi th | |||
4 | * this wo rk for add itional in formation regarding copyright ownership. | 4 | * this wo rk for add itional in formation regarding copyright ownership. | |||
5 | * The ASF licenses this file to You und er the Apa che Licens e, Version 2.0 | 5 | * The ASF licenses this file to You und er the Apa che Licens e, Version 2.0 | |||
6 | * (the "L icense"); you may no t use this file exce pt in comp liance wit h | 6 | * (the "L icense"); you may no t use this file exce pt in comp liance wit h | |||
7 | * the Lic ense. You may obtai n a copy o f the Lice nse at | 7 | * the Lic ense. You may obtai n a copy o f the Lice nse at | |||
8 | * | 8 | * | |||
9 | * ht tp://www.a pache.org/ licenses/L ICENSE-2.0 | 9 | * ht tp://www.a pache.org/ licenses/L ICENSE-2.0 | |||
10 | * | 10 | * | |||
11 | * Unless required b y applicab le law or agreed to in writing , software | 11 | * Unless required b y applicab le law or agreed to in writing , software | |||
12 | * distrib uted under the Licen se is dist ributed on an "AS IS " BASIS, | 12 | * distrib uted under the Licen se is dist ributed on an "AS IS " BASIS, | |||
13 | * WITHOUT WARRANTIE S OR CONDI TIONS OF A NY KIND, e ither expr ess or imp lied. | 13 | * WITHOUT WARRANTIE S OR CONDI TIONS OF A NY KIND, e ither expr ess or imp lied. | |||
14 | * See the License f or the spe cific lang uage gover ning permi ssions and | 14 | * See the License f or the spe cific lang uage gover ning permi ssions and | |||
15 | * limitat ions under the Licen se. | 15 | * limitat ions under the Licen se. | |||
16 | */ | 16 | */ | |||
17 | 17 | |||||
18 | 18 | |||||
19 | package or g.apache.c atalina.au thenticato r; | 19 | package or g.apache.c atalina.au thenticato r; | |||
20 | 20 | |||||
21 | 21 | |||||
22 | import jav a.io.IOExc eption; | 22 | import jav a.io.IOExc eption; | |||
23 | import jav a.nio.char set.Charse t; | 23 | import jav a.nio.char set.Charse t; | |||
24 | import jav a.nio.char set.Standa rdCharsets ; | 24 | import jav a.nio.char set.Standa rdCharsets ; | |||
25 | import jav a.security .Principal ; | 25 | import jav a.security .Principal ; | |||
26 | 26 | |||||
27 | import jav ax.servlet .http.Http ServletReq uest; | 27 | import jav ax.servlet .http.Http ServletReq uest; | |||
28 | import jav ax.servlet .http.Http ServletRes ponse; | 28 | import jav ax.servlet .http.Http ServletRes ponse; | |||
29 | 29 | |||||
30 | import org .apache.ca talina.con nector.Req uest; | 30 | import org .apache.ca talina.con nector.Req uest; | |||
31 | import org .apache.ju li.logging .Log; | 31 | import org .apache.ju li.logging .Log; | |||
32 | import org .apache.ju li.logging .LogFactor y; | 32 | import org .apache.ju li.logging .LogFactor y; | |||
33 | import org .apache.to mcat.util. buf.ByteCh unk; | 33 | import org .apache.to mcat.util. buf.ByteCh unk; | |||
34 | import org .apache.to mcat.util. buf.Messag eBytes; | 34 | import org .apache.to mcat.util. buf.Messag eBytes; | |||
35 | import org .apache.to mcat.util. codec.bina ry.Base64; | 35 | import org .apache.to mcat.util. codec.bina ry.Base64; | |||
36 | 36 | |||||
37 | 37 | |||||
38 | 38 | |||||
39 | /** | 39 | /** | |||
40 | * An <b>A uthenticat or</b> and <b>Valve< /b> implem entation o f HTTP BAS IC | 40 | * An <b>A uthenticat or</b> and <b>Valve< /b> implem entation o f HTTP BAS IC | |||
41 | * Authent ication, a s outlined in RFC 26 17: "HTTP Authentic ation: Bas ic | 41 | * Authent ication, a s outlined in RFC 26 17: "HTTP Authentic ation: Bas ic | |||
42 | * and Dig est Access Authentic ation." | 42 | * and Dig est Access Authentic ation." | |||
43 | * | 43 | * | |||
44 | * @author Craig R. McClanahan | 44 | * @author Craig R. McClanahan | |||
45 | */ | 45 | */ | |||
46 | public cla ss BasicAu thenticato r extends Authentica torBase { | 46 | public cla ss BasicAu thenticato r extends Authentica torBase { | |||
47 | 47 | |||||
48 | privat e static f inal Log l og = LogFa ctory.getL og(BasicAu thenticato r.class); | 48 | privat e static f inal Log l og = LogFa ctory.getL og(BasicAu thenticato r.class); | |||
49 | 49 | |||||
50 | privat e Charset charset = StandardCh arsets.ISO _8859_1; | 50 | privat e Charset charset = StandardCh arsets.ISO _8859_1; | |||
51 | privat e String c harsetStri ng = null; | 51 | privat e String c harsetStri ng = null; | |||
52 | 52 | |||||
53 | 53 | |||||
54 | public String ge tCharset() { | 54 | public String ge tCharset() { | |||
55 | re turn chars etString; | 55 | re turn chars etString; | |||
56 | } | 56 | } | |||
57 | 57 | |||||
58 | 58 | |||||
59 | public void setC harset(Str ing charse tString) { | 59 | public void setC harset(Str ing charse tString) { | |||
60 | // Only acce ptable opt ions are n ull, "" or "UTF-8" ( case insen sitive) | 60 | // Only acce ptable opt ions are n ull, "" or "UTF-8" ( case insen sitive) | |||
61 | if (charsetS tring == n ull || cha rsetString .isEmpty() ) { | 61 | if (charsetS tring == n ull || cha rsetString .isEmpty() ) { | |||
62 | charset = Standard Charsets.I SO_8859_1; | 62 | charset = Standard Charsets.I SO_8859_1; | |||
63 | } else if (" UTF-8".equ alsIgnoreC ase(charse tString)) { | 63 | } else if (" UTF-8".equ alsIgnoreC ase(charse tString)) { | |||
64 | charset = Standard Charsets.U TF_8; | 64 | charset = Standard Charsets.U TF_8; | |||
65 | } else { | 65 | } else { | |||
66 | throw ne w IllegalA rgumentExc eption(sm. getString( "basicAuth enticator. invalidCha rset")); | 66 | throw ne w IllegalA rgumentExc eption(sm. getString( "basicAuth enticator. invalidCha rset")); | |||
67 | } | 67 | } | |||
68 | th is.charset String = c harsetStri ng; | 68 | th is.charset String = c harsetStri ng; | |||
69 | } | 69 | } | |||
70 | 70 | |||||
71 | 71 | |||||
72 | @Overr ide | 72 | @Overr ide | |||
73 |
p
ubli
c
|
73 | p rote c ted boolean doA uthenticat e(Request request, H ttpServlet Response r esponse) | |||
74 | throws I OException { | 74 | throws I OException { | |||
75 | 75 | |||||
76 | if (checkFor CachedAuth entication (request, response, true)) { | 76 | if (checkFor CachedAuth entication (request, response, true)) { | |||
77 | return t rue; | 77 | return t rue; | |||
78 | } | 78 | } | |||
79 | 79 | |||||
80 | // Validate any creden tials alre ady includ ed with th is request | 80 | // Validate any creden tials alre ady includ ed with th is request | |||
81 | Me ssageBytes authoriza tion = | 81 | Me ssageBytes authoriza tion = | |||
82 | request. getCoyoteR equest().g etMimeHead ers() | 82 | request. getCoyoteR equest().g etMimeHead ers() | |||
83 | .getValu e("authori zation"); | 83 | .getValu e("authori zation"); | |||
84 | 84 | |||||
85 | if (authoriz ation != n ull) { | 85 | if (authoriz ation != n ull) { | |||
86 | authoriz ation.toBy tes(); | 86 | authoriz ation.toBy tes(); | |||
87 | ByteChun k authoriz ationBC = authorizat ion.getByt eChunk(); | 87 | ByteChun k authoriz ationBC = authorizat ion.getByt eChunk(); | |||
88 | BasicCre dentials c redentials = null; | 88 | BasicCre dentials c redentials = null; | |||
89 | try { | 89 | try { | |||
90 | cred entials = new BasicC redentials (authoriza tionBC, ch arset); | 90 | cred entials = new BasicC redentials (authoriza tionBC, ch arset); | |||
91 | Stri ng usernam e = creden tials.getU sername(); | 91 | Stri ng usernam e = creden tials.getU sername(); | |||
92 | Stri ng passwor d = creden tials.getP assword(); | 92 | Stri ng passwor d = creden tials.getP assword(); | |||
93 | 93 | |||||
94 | Prin cipal prin cipal = co ntext.getR ealm().aut henticate( username, password); | 94 | Prin cipal prin cipal = co ntext.getR ealm().aut henticate( username, password); | |||
95 | if ( principal != null) { | 95 | if ( principal != null) { | |||
96 | register(r equest, re sponse, pr incipal, | 96 | register(r equest, re sponse, pr incipal, | |||
97 | HttpSe rvletReque st.BASIC_A UTH, usern ame, passw ord); | 97 | HttpSe rvletReque st.BASIC_A UTH, usern ame, passw ord); | |||
98 | return ( true ) ; | 98 |
return
|
|||
99 | } | 99 | } | |||
100 | } | 100 | } | |||
101 | catch (I llegalArgu mentExcept ion iae) { | 101 | catch (I llegalArgu mentExcept ion iae) { | |||
102 | if ( log.isDebu gEnabled() ) { | 102 | if ( log.isDebu gEnabled() ) { | |||
103 | log.debug( "Invalid A uthorizati on" + iae. getMessage ()); | 103 | log.debug( "Invalid A uthorizati on" + iae. getMessage ()); | |||
104 | } | 104 | } | |||
105 | } | 105 | } | |||
106 | } | 106 | } | |||
107 | 107 | |||||
108 | // the reque st could n ot be auth enticated, so reissu e the chal lenge | 108 | // the reque st could n ot be auth enticated, so reissu e the chal lenge | |||
109 | St ringBuilde r value = new String Builder(16 ); | 109 | St ringBuilde r value = new String Builder(16 ); | |||
110 | va lue.append ("Basic re alm=\""); | 110 | va lue.append ("Basic re alm=\""); | |||
111 | va lue.append (getRealmN ame(contex t)); | 111 | va lue.append (getRealmN ame(contex t)); | |||
112 | va lue.append ('\"'); | 112 | va lue.append ('\"'); | |||
113 | if (charsetS tring != n ull && !ch arsetStrin g.isEmpty( )) { | 113 | if (charsetS tring != n ull && !ch arsetStrin g.isEmpty( )) { | |||
114 | value.ap pend(", ch arset="); | 114 | value.ap pend(", ch arset="); | |||
115 | value.ap pend(chars etString); | 115 | value.ap pend(chars etString); | |||
116 | } | 116 | } | |||
117 | re sponse.set Header(AUT H_HEADER_N AME, value .toString( )); | 117 | re sponse.set Header(AUT H_HEADER_N AME, value .toString( )); | |||
118 | re sponse.sen dError(Htt pServletRe sponse.SC_ UNAUTHORIZ ED); | 118 | re sponse.sen dError(Htt pServletRe sponse.SC_ UNAUTHORIZ ED); | |||
119 | return ( false ) ; | 119 |
return
|
|||
120 | 120 | |||||
121 | } | 121 | } | |||
122 | 122 | |||||
123 | @Overr ide | 123 | @Overr ide | |||
124 | protec ted String getAuthMe thod() { | 124 | protec ted String getAuthMe thod() { | |||
125 | re turn HttpS ervletRequ est.BASIC_ AUTH; | 125 | re turn HttpS ervletRequ est.BASIC_ AUTH; | |||
126 | } | 126 | } | |||
127 | 127 | |||||
128 | 128 | |||||
129 | /** | 129 | /** | |||
130 | * Par ser for an HTTP Auth orization header for BASIC aut henticatio n | 130 | * Par ser for an HTTP Auth orization header for BASIC aut henticatio n | |||
131 | * as per RFC 26 17 section 2, and th e Base64 e ncoded cre dentials a s | 131 | * as per RFC 26 17 section 2, and th e Base64 e ncoded cre dentials a s | |||
132 | * per RFC 2045 section 6. 8. | 132 | * per RFC 2045 section 6. 8. | |||
133 | */ | 133 | */ | |||
134 | p rote c ted static cl ass BasicC redentials { | 134 |
p
ubli
c
|
|||
135 | 135 | |||||
136 | // the only authentica tion metho d supporte d by this parser | 136 | // the only authentica tion metho d supporte d by this parser | |||
137 | // note: we include si ngle white space as its delimi ter | 137 | // note: we include si ngle white space as its delimi ter | |||
138 | pr ivate stat ic final S tring METH OD = "basi c "; | 138 | pr ivate stat ic final S tring METH OD = "basi c "; | |||
139 | 139 | |||||
140 | pr ivate fina l Charset charset; | 140 | pr ivate fina l Charset charset; | |||
141 | pr ivate fina l ByteChun k authoriz ation; | 141 | pr ivate fina l ByteChun k authoriz ation; | |||
142 | pr ivate fina l int init ialOffset; | 142 | pr ivate fina l int init ialOffset; | |||
143 | pr ivate int base64blob Offset; | 143 | pr ivate int base64blob Offset; | |||
144 | pr ivate int base64blob Length; | 144 | pr ivate int base64blob Length; | |||
145 | 145 | |||||
146 | pr ivate Stri ng usernam e = null; | 146 | pr ivate Stri ng usernam e = null; | |||
147 | pr ivate Stri ng passwor d = null; | 147 | pr ivate Stri ng passwor d = null; | |||
148 | /* * | 148 | /* * | |||
149 | * Parse the HTTP Auth orization header for BASIC aut henticatio n | 149 | * Parse the HTTP Auth orization header for BASIC aut henticatio n | |||
150 | * as per RF C 2617 sec tion 2, an d the Base 64 encoded credentia ls | 150 | * as per RF C 2617 sec tion 2, an d the Base 64 encoded credentia ls | |||
151 | * as per RF C 2045 sec tion 6.8. | 151 | * as per RF C 2045 sec tion 6.8. | |||
152 | * | 152 | * | |||
153 | * @param in put The header val ue to pars e in-place | 153 | * @param in put The header val ue to pars e in-place | |||
154 | * @param ch arset The character set to use to conver t the byte s to a | 154 | * @param ch arset The character set to use to conver t the byte s to a | |||
155 | * stri ng | 155 | * stri ng | |||
156 | * | 156 | * | |||
157 | * @throws I llegalArgu mentExcept ion If the header do es not con form | 157 | * @throws I llegalArgu mentExcept ion If the header do es not con form | |||
158 | * to RFC 2617 | 158 | * to RFC 2617 | |||
159 | * / | 159 | * / | |||
160 | pu blic Basic Credential s(ByteChun k input, C harset cha rset) thro ws Illegal ArgumentEx ception { | 160 | pu blic Basic Credential s(ByteChun k input, C harset cha rset) thro ws Illegal ArgumentEx ception { | |||
161 | authoriz ation = in put; | 161 | authoriz ation = in put; | |||
162 | initialO ffset = in put.getOff set(); | 162 | initialO ffset = in put.getOff set(); | |||
163 | this.cha rset = cha rset; | 163 | this.cha rset = cha rset; | |||
164 | 164 | |||||
165 | parseMet hod(); | 165 | parseMet hod(); | |||
166 | byte[] d ecoded = p arseBase64 (); | 166 | byte[] d ecoded = p arseBase64 (); | |||
167 | parseCre dentials(d ecoded); | 167 | parseCre dentials(d ecoded); | |||
168 | } | 168 | } | |||
169 | 169 | |||||
170 | /* * | 170 | /* * | |||
171 | * Trivial a ccessor. | 171 | * Trivial a ccessor. | |||
172 | * | 172 | * | |||
173 | * @return the decode d username token as a String, which is | 173 | * @return the decode d username token as a String, which is | |||
174 | * never be < code>null< /code>, bu t can be e mpty. | 174 | * never be < code>null< /code>, bu t can be e mpty. | |||
175 | * / | 175 | * / | |||
176 | pu blic Strin g getUsern ame() { | 176 | pu blic Strin g getUsern ame() { | |||
177 | return u sername; | 177 | return u sername; | |||
178 | } | 178 | } | |||
179 | 179 | |||||
180 | /* * | 180 | /* * | |||
181 | * Trivial a ccessor. | 181 | * Trivial a ccessor. | |||
182 | * | 182 | * | |||
183 | * @return the decode d password token as a String, or <code>n ull</code> | 183 | * @return the decode d password token as a String, or <code>n ull</code> | |||
184 | * if no pass word was f ound in th e credenti als. | 184 | * if no pass word was f ound in th e credenti als. | |||
185 | * / | 185 | * / | |||
186 | pu blic Strin g getPassw ord() { | 186 | pu blic Strin g getPassw ord() { | |||
187 | return p assword; | 187 | return p assword; | |||
188 | } | 188 | } | |||
189 | 189 | |||||
190 | /* | 190 | /* | |||
191 | * The autho rization m ethod stri ng is case -insensiti ve and mus t | 191 | * The autho rization m ethod stri ng is case -insensiti ve and mus t | |||
192 | * hae at le ast one sp ace charac ter as a d elimiter. | 192 | * hae at le ast one sp ace charac ter as a d elimiter. | |||
193 | * / | 193 | * / | |||
194 | pr ivate void parseMeth od() throw s IllegalA rgumentExc eption { | 194 | pr ivate void parseMeth od() throw s IllegalA rgumentExc eption { | |||
195 | if (auth orization. startsWith IgnoreCase (METHOD, 0 )) { | 195 | if (auth orization. startsWith IgnoreCase (METHOD, 0 )) { | |||
196 | // s tep past t he auth me thod name | 196 | // s tep past t he auth me thod name | |||
197 | base 64blobOffs et = initi alOffset + METHOD.le ngth(); | 197 | base 64blobOffs et = initi alOffset + METHOD.le ngth(); | |||
198 | base 64blobLeng th = autho rization.g etLength() - METHOD. length(); | 198 | base 64blobLeng th = autho rization.g etLength() - METHOD. length(); | |||
199 | } | 199 | } | |||
200 | else { | 200 | else { | |||
201 | // i s this pos sible, or permitted? | 201 | // i s this pos sible, or permitted? | |||
202 | thro w new Ille galArgumen tException ( | 202 | thro w new Ille galArgumen tException ( | |||
203 | "Autho rization h eader meth od is not \"Basic\"" ); | 203 | "Autho rization h eader meth od is not \"Basic\"" ); | |||
204 | } | 204 | } | |||
205 | } | 205 | } | |||
206 | /* | 206 | /* | |||
207 | * Decode th e base64-u ser-pass t oken, whic h RFC 2617 states | 207 | * Decode th e base64-u ser-pass t oken, whic h RFC 2617 states | |||
208 | * can be lo nger than the 76 cha racters pe r line lim it defined | 208 | * can be lo nger than the 76 cha racters pe r line lim it defined | |||
209 | * in RFC 20 45. The ba se64 decod er will ig nore embed ded line | 209 | * in RFC 20 45. The ba se64 decod er will ig nore embed ded line | |||
210 | * break cha racters as well as s urplus sur rounding w hite space . | 210 | * break cha racters as well as s urplus sur rounding w hite space . | |||
211 | * / | 211 | * / | |||
212 | pr ivate byte [] parseBa se64() thr ows Illega lArgumentE xception { | 212 | pr ivate byte [] parseBa se64() thr ows Illega lArgumentE xception { | |||
213 | byte[] d ecoded = B ase64.deco deBase64( | 213 | byte[] d ecoded = B ase64.deco deBase64( | |||
214 | author ization.ge tBuffer(), | 214 | author ization.ge tBuffer(), | |||
215 | base64 blobOffset , base64bl obLength); | 215 | base64 blobOffset , base64bl obLength); | |||
216 | // rest ore origin al offset | 216 | // rest ore origin al offset | |||
217 | authoriz ation.setO ffset(init ialOffset) ; | 217 | authoriz ation.setO ffset(init ialOffset) ; | |||
218 | if (deco ded == nul l) { | 218 | if (deco ded == nul l) { | |||
219 | thro w new Ille galArgumen tException ( | 219 | thro w new Ille galArgumen tException ( | |||
220 | "Basic Authoriza tion crede ntials are not Base6 4"); | 220 | "Basic Authoriza tion crede ntials are not Base6 4"); | |||
221 | } | 221 | } | |||
222 | return d ecoded; | 222 | return d ecoded; | |||
223 | } | 223 | } | |||
224 | 224 | |||||
225 | /* | 225 | /* | |||
226 | * Extract t he mandato ry usernam e token an d separate it from t he | 226 | * Extract t he mandato ry usernam e token an d separate it from t he | |||
227 | * optional password t oken. Tole rate surpl us surroun ding white space. | 227 | * optional password t oken. Tole rate surpl us surroun ding white space. | |||
228 | * / | 228 | * / | |||
229 | pr ivate void parseCred entials(by te[] decod ed) | 229 | pr ivate void parseCred entials(by te[] decod ed) | |||
230 | thro ws Illegal ArgumentEx ception { | 230 | thro ws Illegal ArgumentEx ception { | |||
231 | 231 | |||||
232 | int colo n = -1; | 232 | int colo n = -1; | |||
233 | for (int i = 0; i < decoded. length; i+ +) { | 233 | for (int i = 0; i < decoded. length; i+ +) { | |||
234 | if ( decoded[i] == ':') { | 234 | if ( decoded[i] == ':') { | |||
235 | colon = i; | 235 | colon = i; | |||
236 | break; | 236 | break; | |||
237 | } | 237 | } | |||
238 | } | 238 | } | |||
239 | 239 | |||||
240 | if (colo n < 0) { | 240 | if (colo n < 0) { | |||
241 | user name = new String(de coded, cha rset); | 241 | user name = new String(de coded, cha rset); | |||
242 | // p assword wi ll remain null! | 242 | // p assword wi ll remain null! | |||
243 | } | 243 | } | |||
244 | else { | 244 | else { | |||
245 | user name = new String(de coded, 0, colon, cha rset); | 245 | user name = new String(de coded, 0, colon, cha rset); | |||
246 | pass word = new String(de coded, col on + 1, de coded.leng th - colon - 1, char set); | 246 | pass word = new String(de coded, col on + 1, de coded.leng th - colon - 1, char set); | |||
247 | // t olerate su rplus whit e space ar ound crede ntials | 247 | // t olerate su rplus whit e space ar ound crede ntials | |||
248 | if ( password.l ength() > 1) { | 248 | if ( password.l ength() > 1) { | |||
249 | password = password. trim(); | 249 | password = password. trim(); | |||
250 | } | 250 | } | |||
251 | } | 251 | } | |||
252 | // toler ate surplu s white sp ace around credentia ls | 252 | // toler ate surplu s white sp ace around credentia ls | |||
253 | if (user name.lengt h() > 1) { | 253 | if (user name.lengt h() > 1) { | |||
254 | user name = use rname.trim (); | 254 | user name = use rname.trim (); | |||
255 | } | 255 | } | |||
256 | } | 256 | } | |||
257 | } | 257 | } | |||
258 | } | 258 | } |
Araxis Merge (but not the data content of this report) is Copyright © 1993–2017 Araxis Ltd (www.araxis.com). All rights reserved.