1 /*
   2  * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.print;
  27 
  28 import java.net.URL;
  29 import java.net.HttpURLConnection;
  30 import java.io.OutputStream;
  31 import java.io.InputStream;
  32 import java.util.ArrayList;
  33 import java.util.HashMap;
  34 import sun.print.IPPPrintService;
  35 import sun.print.CustomMediaSizeName;
  36 import sun.print.CustomMediaTray;
  37 import javax.print.attribute.standard.Media;
  38 import javax.print.attribute.standard.MediaSizeName;
  39 import javax.print.attribute.standard.MediaSize;
  40 import javax.print.attribute.standard.MediaTray;
  41 import javax.print.attribute.standard.MediaPrintableArea;
  42 import javax.print.attribute.standard.PrinterResolution;
  43 import javax.print.attribute.Size2DSyntax;
  44 import javax.print.attribute.Attribute;
  45 import javax.print.attribute.EnumSyntax;
  46 import javax.print.attribute.standard.PrinterName;
  47 
  48 
  49 public class CUPSPrinter  {
  50     private static final String debugPrefix = "CUPSPrinter>> ";
  51     private static final double PRINTER_DPI = 72.0;
  52     private boolean initialized;
  53     private static native String getCupsServer();
  54     private static native int getCupsPort();
  55     private static native String getCupsDefaultPrinter();
  56     private static native boolean canConnect(String server, int port);
  57     private static native boolean initIDs();
  58     // These functions need to be synchronized as
  59     // CUPS does not support multi-threading.
  60     private static synchronized native String[] getMedia(String printer);
  61     private static synchronized native float[] getPageSizes(String printer);
  62     private static synchronized native void
  63         getResolutions(String printer, ArrayList<Integer> resolutionList);
  64     //public static boolean useIPPMedia = false; will be used later
  65 
  66     private MediaPrintableArea[] cupsMediaPrintables;
  67     private MediaSizeName[] cupsMediaSNames;
  68     private CustomMediaSizeName[] cupsCustomMediaSNames;
  69     private MediaTray[] cupsMediaTrays;
  70 
  71     public  int nPageSizes = 0;
  72     public  int nTrays = 0;
  73     private  String[] media;
  74     private  float[] pageSizes;
  75     int[]   resolutionsArray;
  76     private String printer;
  77 
  78     private static boolean libFound;
  79     private static String cupsServer = null;
  80     private static int cupsPort = 0;
  81 
  82     static {
  83         // load awt library to access native code
  84         jdk.internal.access.SharedSecrets.getJavaLangAccess().loadLibrary("awt");
  85         libFound = initIDs();
  86         if (libFound) {
  87            cupsServer = getCupsServer();
  88            cupsPort = getCupsPort();
  89         }
  90     }
  91 
  92 
  93     CUPSPrinter (String printerName) {
  94         if (printerName == null) {
  95             throw new IllegalArgumentException("null printer name");
  96         }
  97         printer = printerName;
  98         cupsMediaSNames = null;
  99         cupsMediaPrintables = null;
 100         cupsMediaTrays = null;
 101         initialized = false;
 102 
 103         if (!libFound) {
 104             throw new RuntimeException("cups lib not found");
 105         } else {
 106             // get page + tray names
 107             media =  getMedia(printer);
 108             if (media == null) {
 109                 // either PPD file is not found or printer is unknown
 110                 throw new RuntimeException("error getting PPD");
 111             }
 112 
 113             // get sizes
 114             pageSizes = getPageSizes(printer);
 115             if (pageSizes != null) {
 116                 nPageSizes = pageSizes.length/6;
 117 
 118                 nTrays = media.length/2-nPageSizes;
 119                 assert (nTrays >= 0);
 120             }
 121             ArrayList<Integer> resolutionList = new ArrayList<>();
 122             getResolutions(printer, resolutionList);
 123             resolutionsArray = new int[resolutionList.size()];
 124             for (int i=0; i < resolutionList.size(); i++) {
 125                 resolutionsArray[i] = resolutionList.get(i);
 126             }
 127         }
 128     }
 129 
 130 
 131     /**
 132      * Returns array of MediaSizeNames derived from PPD.
 133      */
 134     MediaSizeName[] getMediaSizeNames() {
 135         initMedia();
 136         return cupsMediaSNames;
 137     }
 138 
 139 
 140     /**
 141      * Returns array of Custom MediaSizeNames derived from PPD.
 142      */
 143     CustomMediaSizeName[] getCustomMediaSizeNames() {
 144         initMedia();
 145         return cupsCustomMediaSNames;
 146     }
 147 
 148     public int getDefaultMediaIndex() {
 149         return ((pageSizes.length >1) ? (int)(pageSizes[pageSizes.length -1]) : 0);
 150     }
 151 
 152     /**
 153      * Returns array of MediaPrintableArea derived from PPD.
 154      */
 155     MediaPrintableArea[] getMediaPrintableArea() {
 156         initMedia();
 157         return cupsMediaPrintables;
 158     }
 159 
 160     /**
 161      * Returns array of MediaTrays derived from PPD.
 162      */
 163     MediaTray[] getMediaTrays() {
 164         initMedia();
 165         return cupsMediaTrays;
 166     }
 167 
 168     /**
 169      * return the raw packed array of supported printer resolutions.
 170      */
 171     int[] getRawResolutions() {
 172         return resolutionsArray;
 173     }
 174 
 175     /**
 176      * Initialize media by translating PPD info to PrintService attributes.
 177      */
 178     private synchronized void initMedia() {
 179         if (initialized) {
 180             return;
 181         } else {
 182             initialized = true;
 183         }
 184 
 185         if (pageSizes == null) {
 186             return;
 187         }
 188 
 189         cupsMediaPrintables = new MediaPrintableArea[nPageSizes];
 190         cupsMediaSNames = new MediaSizeName[nPageSizes];
 191         cupsCustomMediaSNames = new CustomMediaSizeName[nPageSizes];
 192 
 193         CustomMediaSizeName msn;
 194         MediaPrintableArea mpa;
 195         float length, width, x, y, w, h;
 196 
 197         // initialize names and printables
 198         for (int i=0; i<nPageSizes; i++) {
 199             // media width and length
 200             width = (float)(pageSizes[i*6]/PRINTER_DPI);
 201             length = (float)(pageSizes[i*6+1]/PRINTER_DPI);
 202             // media printable area
 203             x = (float)(pageSizes[i*6+2]/PRINTER_DPI);
 204             h = (float)(pageSizes[i*6+3]/PRINTER_DPI);
 205             w = (float)(pageSizes[i*6+4]/PRINTER_DPI);
 206             y = (float)(pageSizes[i*6+5]/PRINTER_DPI);
 207 
 208             msn = new CustomMediaSizeName(media[i*2], media[i*2+1],
 209                                           width, length);
 210 
 211             // add to list of standard MediaSizeNames
 212             if ((cupsMediaSNames[i] = msn.getStandardMedia()) == null) {
 213                 // add custom if no matching standard media
 214                 cupsMediaSNames[i] = msn;
 215 
 216                 // add this new custom msn to MediaSize array
 217                 if ((width > 0.0) && (length > 0.0)) {
 218                     try {
 219                     new MediaSize(width, length,
 220                                   Size2DSyntax.INCH, msn);
 221                     } catch (IllegalArgumentException e) {
 222                         /* PDF printer in Linux for Ledger paper causes
 223                         "IllegalArgumentException: X dimension > Y dimension".
 224                         We rotate based on IPP spec. */
 225                         new MediaSize(length, width, Size2DSyntax.INCH, msn);
 226                     }
 227                 }
 228             }
 229 
 230             // add to list of custom MediaSizeName
 231             // for internal use of IPPPrintService
 232             cupsCustomMediaSNames[i] = msn;
 233 
 234             mpa = null;
 235             try {
 236                 mpa = new MediaPrintableArea(x, y, w, h,
 237                                              MediaPrintableArea.INCH);
 238             } catch (IllegalArgumentException e) {
 239                 if (width > 0 && length > 0) {
 240                     mpa = new MediaPrintableArea(0, 0, width, length,
 241                                              MediaPrintableArea.INCH);
 242                 }
 243             }
 244             cupsMediaPrintables[i] = mpa;
 245         }
 246 
 247         // initialize trays
 248         cupsMediaTrays = new MediaTray[nTrays];
 249 
 250         MediaTray mt;
 251         for (int i=0; i<nTrays; i++) {
 252             mt = new CustomMediaTray(media[(nPageSizes+i)*2],
 253                                      media[(nPageSizes+i)*2+1]);
 254             cupsMediaTrays[i] = mt;
 255         }
 256 
 257     }
 258 
 259     /**
 260      * Get CUPS default printer using IPP.
 261      * Returns 2 values - index 0 is printer name, index 1 is the uri.
 262      */
 263     static String[] getDefaultPrinter() {
 264         // Try to get user/lpoptions-defined printer name from CUPS
 265         // if not user-set, then go for server default destination
 266         String[] printerInfo = new String[2];
 267         printerInfo[0] = getCupsDefaultPrinter();
 268 
 269         if (printerInfo[0] != null) {
 270             printerInfo[1] = null;
 271             return printerInfo.clone();
 272         }
 273         try {
 274             URL url = new URL("http", getServer(), getPort(), "");
 275             final HttpURLConnection urlConnection =
 276                 IPPPrintService.getIPPConnection(url);
 277 
 278             if (urlConnection != null) {
 279                 OutputStream os = java.security.AccessController.
 280                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
 281                         public OutputStream run() {
 282                             try {
 283                                 return urlConnection.getOutputStream();
 284                             } catch (Exception e) {
 285                                IPPPrintService.debug_println(debugPrefix+e);
 286                             }
 287                             return null;
 288                         }
 289                     });
 290 
 291                 if (os == null) {
 292                     return null;
 293                 }
 294 
 295                 AttributeClass[] attCl = {
 296                     AttributeClass.ATTRIBUTES_CHARSET,
 297                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
 298                     new AttributeClass("requested-attributes",
 299                                        AttributeClass.TAG_URI,
 300                                        "printer-uri")
 301                 };
 302 
 303                 if (IPPPrintService.writeIPPRequest(os,
 304                                         IPPPrintService.OP_CUPS_GET_DEFAULT,
 305                                         attCl)) {
 306 
 307                     HashMap<String, AttributeClass> defaultMap = null;
 308 
 309                     InputStream is = urlConnection.getInputStream();
 310                     HashMap<String, AttributeClass>[] responseMap = IPPPrintService.readIPPResponse(
 311                                          is);
 312                     is.close();
 313 
 314                     if (responseMap != null && responseMap.length > 0) {
 315                         defaultMap = responseMap[0];
 316                     } else {
 317                        IPPPrintService.debug_println(debugPrefix+
 318                            " empty response map for GET_DEFAULT.");
 319                     }
 320 
 321                     if (defaultMap == null) {
 322                         os.close();
 323                         urlConnection.disconnect();
 324 
 325                         /* CUPS on OS X, as initially configured, considers the
 326                          * default printer to be the last one used that's
 327                          * presently available. So if no default was
 328                          * reported, exec lpstat -d which has all the Apple
 329                          * special behaviour for this built in.
 330                          */
 331                          if (PrintServiceLookupProvider.isMac()) {
 332                              printerInfo[0] = PrintServiceLookupProvider.
 333                                                    getDefaultPrinterNameSysV();
 334                              printerInfo[1] = null;
 335                              return printerInfo.clone();
 336                          } else {
 337                              return null;
 338                          }
 339                     }
 340 
 341 
 342                     AttributeClass attribClass = defaultMap.get("printer-name");
 343 
 344                     if (attribClass != null) {
 345                         printerInfo[0] = attribClass.getStringValue();
 346                         attribClass = defaultMap.get("printer-uri-supported");
 347                         IPPPrintService.debug_println(debugPrefix+
 348                           "printer-uri-supported="+attribClass);
 349                         if (attribClass != null) {
 350                             printerInfo[1] = attribClass.getStringValue();
 351                         } else {
 352                             printerInfo[1] = null;
 353                         }
 354                         os.close();
 355                         urlConnection.disconnect();
 356                         return printerInfo.clone();
 357                     }
 358                 }
 359                 os.close();
 360                 urlConnection.disconnect();
 361             }
 362         } catch (Exception e) {
 363         }
 364         return null;
 365     }
 366 
 367 
 368     /**
 369      * Get list of all CUPS printers using IPP.
 370      */
 371     static String[] getAllPrinters() {
 372         try {
 373             URL url = new URL("http", getServer(), getPort(), "");
 374 
 375             final HttpURLConnection urlConnection =
 376                 IPPPrintService.getIPPConnection(url);
 377 
 378             if (urlConnection != null) {
 379                 OutputStream os = java.security.AccessController.
 380                     doPrivileged(new java.security.PrivilegedAction<OutputStream>() {
 381                         public OutputStream run() {
 382                             try {
 383                                 return urlConnection.getOutputStream();
 384                             } catch (Exception e) {
 385                             }
 386                             return null;
 387                         }
 388                     });
 389 
 390                 if (os == null) {
 391                     return null;
 392                 }
 393 
 394                 AttributeClass[] attCl = {
 395                     AttributeClass.ATTRIBUTES_CHARSET,
 396                     AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
 397                     new AttributeClass("requested-attributes",
 398                                        AttributeClass.TAG_KEYWORD,
 399                                        "printer-uri-supported")
 400                 };
 401 
 402                 if (IPPPrintService.writeIPPRequest(os,
 403                                 IPPPrintService.OP_CUPS_GET_PRINTERS, attCl)) {
 404 
 405                     InputStream is = urlConnection.getInputStream();
 406                     HashMap<String, AttributeClass>[] responseMap =
 407                         IPPPrintService.readIPPResponse(is);
 408 
 409                     is.close();
 410                     os.close();
 411                     urlConnection.disconnect();
 412 
 413                     if (responseMap == null || responseMap.length == 0) {
 414                         return null;
 415                     }
 416 
 417                     ArrayList<String> printerNames = new ArrayList<>();
 418                     for (int i=0; i< responseMap.length; i++) {
 419                         AttributeClass attribClass =
 420                             responseMap[i].get("printer-uri-supported");
 421 
 422                         if (attribClass != null) {
 423                             String nameStr = attribClass.getStringValue();
 424                             printerNames.add(nameStr);
 425                         }
 426                     }
 427                     return printerNames.toArray(new String[] {});
 428                 } else {
 429                     os.close();
 430                     urlConnection.disconnect();
 431                 }
 432             }
 433 
 434         } catch (Exception e) {
 435         }
 436         return null;
 437 
 438     }
 439 
 440     /**
 441      * Returns CUPS server name.
 442      */
 443     public static String getServer() {
 444         return cupsServer;
 445     }
 446 
 447     /**
 448      * Returns CUPS port number.
 449      */
 450     public static int getPort() {
 451         return cupsPort;
 452     }
 453 
 454     /**
 455      * Detects if CUPS is running.
 456      */
 457     public static boolean isCupsRunning() {
 458         IPPPrintService.debug_println(debugPrefix+"libFound "+libFound);
 459         if (libFound) {
 460             IPPPrintService.debug_println(debugPrefix+"CUPS server "+getServer()+
 461                                           " port "+getPort());
 462             return canConnect(getServer(), getPort());
 463         } else {
 464             return false;
 465         }
 466     }
 467 
 468 
 469 }