1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | * |
10 | * This software is licensed as described in the file COPYING, which |
11 | * you should have received as part of this distribution. The terms |
12 | * are also available at https://curl.haxx.se/docs/copyright.html. |
13 | * |
14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | * copies of the Software, and permit persons to whom the Software is |
16 | * furnished to do so, under the terms of the COPYING file. |
17 | * |
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | * KIND, either express or implied. |
20 | * |
21 | ***************************************************************************/ |
22 | #include "tool_setup.h" |
23 | |
24 | #define ENABLE_CURLX_PRINTF |
25 | /* use our own printf() functions */ |
26 | #include "curlx.h" |
27 | |
28 | #include "tool_cfgable.h" |
29 | #include "tool_msgs.h" |
30 | #include "tool_cb_wrt.h" |
31 | #include "tool_operate.h" |
32 | |
33 | #include "memdebug.h" /* keep this as LAST include */ |
34 | |
35 | /* create a local file for writing, return TRUE on success */ |
36 | bool tool_create_output_file(struct OutStruct *outs) |
37 | { |
38 | struct GlobalConfig *global = outs->config->global; |
39 | FILE *file; |
40 | |
41 | if(!outs->filename || !*outs->filename) { |
42 | warnf(global, "Remote filename has no length!\n" ); |
43 | return FALSE; |
44 | } |
45 | |
46 | if(outs->is_cd_filename) { |
47 | /* don't overwrite existing files */ |
48 | file = fopen(outs->filename, "rb" ); |
49 | if(file) { |
50 | fclose(file); |
51 | warnf(global, "Refusing to overwrite %s: %s\n" , outs->filename, |
52 | strerror(EEXIST)); |
53 | return FALSE; |
54 | } |
55 | } |
56 | |
57 | /* open file for writing */ |
58 | file = fopen(outs->filename, "wb" ); |
59 | if(!file) { |
60 | warnf(global, "Failed to create the file %s: %s\n" , outs->filename, |
61 | strerror(errno)); |
62 | return FALSE; |
63 | } |
64 | outs->s_isreg = TRUE; |
65 | outs->fopened = TRUE; |
66 | outs->stream = file; |
67 | outs->bytes = 0; |
68 | outs->init = 0; |
69 | return TRUE; |
70 | } |
71 | |
72 | /* |
73 | ** callback for CURLOPT_WRITEFUNCTION |
74 | */ |
75 | |
76 | size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) |
77 | { |
78 | size_t rc; |
79 | struct per_transfer *per = userdata; |
80 | struct OutStruct *outs = &per->outs; |
81 | struct OperationConfig *config = outs->config; |
82 | size_t bytes = sz * nmemb; |
83 | bool is_tty = config->global->isatty; |
84 | #ifdef WIN32 |
85 | CONSOLE_SCREEN_BUFFER_INFO console_info; |
86 | intptr_t fhnd; |
87 | #endif |
88 | |
89 | /* |
90 | * Once that libcurl has called back tool_write_cb() the returned value |
91 | * is checked against the amount that was intended to be written, if |
92 | * it does not match then it fails with CURLE_WRITE_ERROR. So at this |
93 | * point returning a value different from sz*nmemb indicates failure. |
94 | */ |
95 | const size_t failure = bytes ? 0 : 1; |
96 | |
97 | #ifdef DEBUGBUILD |
98 | { |
99 | char *tty = curlx_getenv("CURL_ISATTY" ); |
100 | if(tty) { |
101 | is_tty = TRUE; |
102 | curl_free(tty); |
103 | } |
104 | } |
105 | |
106 | if(config->show_headers) { |
107 | if(bytes > (size_t)CURL_MAX_HTTP_HEADER) { |
108 | warnf(config->global, "Header data size exceeds single call write " |
109 | "limit!\n" ); |
110 | return failure; |
111 | } |
112 | } |
113 | else { |
114 | if(bytes > (size_t)CURL_MAX_WRITE_SIZE) { |
115 | warnf(config->global, "Data size exceeds single call write limit!\n" ); |
116 | return failure; |
117 | } |
118 | } |
119 | |
120 | { |
121 | /* Some internal congruency checks on received OutStruct */ |
122 | bool check_fails = FALSE; |
123 | if(outs->filename) { |
124 | /* regular file */ |
125 | if(!*outs->filename) |
126 | check_fails = TRUE; |
127 | if(!outs->s_isreg) |
128 | check_fails = TRUE; |
129 | if(outs->fopened && !outs->stream) |
130 | check_fails = TRUE; |
131 | if(!outs->fopened && outs->stream) |
132 | check_fails = TRUE; |
133 | if(!outs->fopened && outs->bytes) |
134 | check_fails = TRUE; |
135 | } |
136 | else { |
137 | /* standard stream */ |
138 | if(!outs->stream || outs->s_isreg || outs->fopened) |
139 | check_fails = TRUE; |
140 | if(outs->alloc_filename || outs->is_cd_filename || outs->init) |
141 | check_fails = TRUE; |
142 | } |
143 | if(check_fails) { |
144 | warnf(config->global, "Invalid output struct data for write callback\n" ); |
145 | return failure; |
146 | } |
147 | } |
148 | #endif |
149 | |
150 | if(!outs->stream && !tool_create_output_file(outs)) |
151 | return failure; |
152 | |
153 | if(is_tty && (outs->bytes < 2000) && !config->terminal_binary_ok) { |
154 | /* binary output to terminal? */ |
155 | if(memchr(buffer, 0, bytes)) { |
156 | warnf(config->global, "Binary output can mess up your terminal. " |
157 | "Use \"--output -\" to tell curl to output it to your terminal " |
158 | "anyway, or consider \"--output <FILE>\" to save to a file.\n" ); |
159 | config->synthetic_error = ERR_BINARY_TERMINAL; |
160 | return failure; |
161 | } |
162 | } |
163 | |
164 | #ifdef WIN32 |
165 | fhnd = _get_osfhandle(fileno(outs->stream)); |
166 | if(isatty(fileno(outs->stream)) && |
167 | GetConsoleScreenBufferInfo((HANDLE)fhnd, &console_info)) { |
168 | DWORD in_len = (DWORD)(sz * nmemb); |
169 | wchar_t* wc_buf; |
170 | DWORD wc_len; |
171 | |
172 | /* calculate buffer size for wide characters */ |
173 | wc_len = MultiByteToWideChar(CP_UTF8, 0, buffer, in_len, NULL, 0); |
174 | wc_buf = (wchar_t*) malloc(wc_len * sizeof(wchar_t)); |
175 | if(!wc_buf) |
176 | return failure; |
177 | |
178 | /* calculate buffer size for multi-byte characters */ |
179 | wc_len = MultiByteToWideChar(CP_UTF8, 0, buffer, in_len, wc_buf, wc_len); |
180 | if(!wc_len) { |
181 | free(wc_buf); |
182 | return failure; |
183 | } |
184 | |
185 | if(!WriteConsoleW( |
186 | (HANDLE) fhnd, |
187 | wc_buf, |
188 | wc_len, |
189 | &wc_len, |
190 | NULL)) { |
191 | free(wc_buf); |
192 | return failure; |
193 | } |
194 | free(wc_buf); |
195 | rc = bytes; |
196 | } |
197 | else |
198 | #endif |
199 | rc = fwrite(buffer, sz, nmemb, outs->stream); |
200 | |
201 | if(bytes == rc) |
202 | /* we added this amount of data to the output */ |
203 | outs->bytes += bytes; |
204 | |
205 | if(config->readbusy) { |
206 | config->readbusy = FALSE; |
207 | curl_easy_pause(per->curl, CURLPAUSE_CONT); |
208 | } |
209 | |
210 | if(config->nobuffer) { |
211 | /* output buffering disabled */ |
212 | int res = fflush(outs->stream); |
213 | if(res) |
214 | return failure; |
215 | } |
216 | |
217 | return rc; |
218 | } |
219 | |