comparison src/main/java/org/monetdb/jdbc/MonetConnection.java @ 613:9397c0b487f8

Normalize CRLF on upload
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Tue, 18 Jan 2022 13:18:43 +0100 (2022-01-18)
parents 6aa38e8c0f2d
children 65641a7cea31
comparison
equal deleted inserted replaced
612:1d44b8a577ca 613:9397c0b487f8
3234 if (uploadHandler == null) { 3234 if (uploadHandler == null) {
3235 return "No file upload handler has been registered with the JDBC driver"; 3235 return "No file upload handler has been registered with the JDBC driver";
3236 } 3236 }
3237 3237
3238 final long linesToSkip = offset >= 1 ? offset - 1 : 0; 3238 final long linesToSkip = offset >= 1 ? offset - 1 : 0;
3239 final Upload handle = new Upload(server, uploadHandler::uploadCancelled); 3239 final Upload handle = new Upload(server, uploadHandler::uploadCancelled, textMode);
3240 final boolean wasFaking = server.setInsertFakePrompts(false); 3240 final boolean wasFaking = server.setInsertFakePrompts(false);
3241 try { 3241 try {
3242 uploadHandler.handleUpload(handle, path, textMode, linesToSkip); 3242 uploadHandler.handleUpload(handle, path, textMode, linesToSkip);
3243 if (!handle.hasBeenUsed()) { 3243 if (!handle.hasBeenUsed()) {
3244 throw new IOException("Call to " + uploadHandler.getClass().getCanonicalName() + ".handleUpload for path '" + path + "' sent neither data nor an error message"); 3244 throw new IOException("Call to " + uploadHandler.getClass().getCanonicalName() + ".handleUpload for path '" + path + "' sent neither data nor an error message");
3324 * Handle passed to {@link UploadHandler} to allow communication with the server 3324 * Handle passed to {@link UploadHandler} to allow communication with the server
3325 */ 3325 */
3326 public static class Upload { 3326 public static class Upload {
3327 private final MapiSocket server; 3327 private final MapiSocket server;
3328 private final Runnable cancellationCallback; 3328 private final Runnable cancellationCallback;
3329 private final boolean textMode;
3329 private PrintStream print = null; 3330 private PrintStream print = null;
3330 private String error = null; 3331 private String error = null;
3331 private int customChunkSize = -1; 3332 private int customChunkSize = -1;
3332 3333
3333 Upload(MapiSocket server, Runnable cancellationCallback) { 3334 Upload(MapiSocket server, Runnable cancellationCallback, boolean textMode) {
3334 this.server = server; 3335 this.server = server;
3335 this.cancellationCallback = cancellationCallback; 3336 this.cancellationCallback = cancellationCallback;
3337 this.textMode = textMode;
3336 } 3338 }
3337 3339
3338 /** 3340 /**
3339 * Send an error message to the server 3341 * Send an error message to the server
3340 * 3342 *
3372 } 3374 }
3373 if (print == null) { 3375 if (print == null) {
3374 try { 3376 try {
3375 final MapiSocket.UploadStream up = customChunkSize >= 0 ? server.uploadStream(customChunkSize) : server.uploadStream(); 3377 final MapiSocket.UploadStream up = customChunkSize >= 0 ? server.uploadStream(customChunkSize) : server.uploadStream();
3376 up.setCancellationCallback(cancellationCallback); 3378 up.setCancellationCallback(cancellationCallback);
3377 print = new PrintStream(up, false, "UTF-8"); 3379 print = new PrintStream(textMode ? new StripCrLfStream(up) : up, false, "UTF-8");
3378 up.write('\n'); 3380 up.write('\n');
3379 } catch (UnsupportedEncodingException e) { 3381 } catch (UnsupportedEncodingException e) {
3380 throw new RuntimeException("The system is guaranteed to support the UTF-8 encoding but apparently it doesn't", e); 3382 throw new RuntimeException("The system is guaranteed to support the UTF-8 encoding but apparently it doesn't", e);
3381 } 3383 }
3382 } 3384 }
3565 /* ignore close error */ 3567 /* ignore close error */
3566 } 3568 }
3567 } 3569 }
3568 } 3570 }
3569 } 3571 }
3572
3573 public static class StripCrLfStream extends FilterOutputStream {
3574 private boolean crPending = false;
3575
3576 public StripCrLfStream(OutputStream out) {
3577 super(out);
3578 }
3579
3580 public boolean pending() {
3581 return this.crPending;
3582 }
3583
3584 @Override
3585 public void write(int b) throws IOException {
3586 if (crPending && b != '\n') {
3587 out.write('\r');
3588 }
3589 if (b != '\r') {
3590 out.write(b);
3591 crPending = false;
3592 } else {
3593 crPending = true;
3594 }
3595 }
3596
3597 @Override
3598 public void write(byte[] b) throws IOException {
3599 this.write(b, 0, b.length);
3600 }
3601
3602 @Override
3603 public void write(byte[] b, int off, int len) throws IOException {
3604 if (len == 0) {
3605 return;
3606 }
3607 if (crPending && b[0] != '\n') {
3608 out.write('\r');
3609 }
3610
3611 // deal with final \r up front
3612 if (b[len - 1] == '\r') {
3613 crPending = true;
3614 len -= 1;
3615 } else {
3616 crPending = false;
3617 }
3618
3619 for (int i = off; i < off + len - 1; i++) {
3620 if (b[i] == '\r' && b[i + 1] == '\n') {
3621 int chunk = i - off;
3622 out.write(b, off, chunk);
3623 // chunk + 1 because we want to skip the \r
3624 len -= chunk + 1;
3625 off += chunk + 1;
3626 // we don't have to look at the \n because we know it's no \r.
3627 i++;
3628 }
3629 }
3630
3631 // write the remainder
3632 out.write(b, off, len);
3633 }
3634
3635 @Override
3636 public void flush() throws IOException {
3637 // we cannot flush our pending CR but we can ask our downstream to flush what we have sent them so far
3638 out.flush();
3639 }
3640
3641 @Override
3642 public void close() throws IOException {
3643 if (crPending) {
3644 out.write('\r');
3645 }
3646 crPending = false;
3647 super.close();
3648 }
3649 }
3570 } 3650 }